Index: /trunk/src/org/openstreetmap/josm/gui/conflict/pair/AbstractMergePanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/pair/AbstractMergePanel.java	(revision 12044)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/pair/AbstractMergePanel.java	(revision 12044)
@@ -0,0 +1,177 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.pair;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.util.List;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * A panel used as tab in the merge dialog.
+ * Contains helper methods for creating merge dialog columns.
+ *
+ * @author Michael Zangl
+ * @since 12044
+ */
+public abstract class AbstractMergePanel extends JPanel {
+
+    /**
+     * A helper class to add a row to the layout. Each row has 6 columns.
+     * @author Michael Zangl
+     */
+    protected static class MergeRow {
+        protected int marginTop = 5;
+
+        public MergeRow() {
+            // allow access from subclasses
+        }
+
+        protected JComponent rowTitle() {
+            return null;
+        }
+
+        protected JComponent mineField() {
+            return null;
+        }
+
+        protected JComponent mineButton() {
+            return null;
+        }
+
+        protected JComponent merged() {
+            return null;
+        }
+
+        protected JComponent theirsButton() {
+            return null;
+        }
+
+        protected JComponent theirsField() {
+            return null;
+        }
+
+        void addTo(AbstractMergePanel panel) {
+            JComponent[] buttons = getColumns();
+            for (int columnIndex = 0; columnIndex < buttons.length; columnIndex++) {
+                if (buttons[columnIndex] != null) {
+                    GBC constraints = GBC.std(columnIndex, panel.currentRow);
+                    addConstraints(constraints, columnIndex);
+                    panel.add(buttons[columnIndex], constraints);
+                }
+            }
+            panel.currentRow++;
+        }
+
+        protected JComponent[] getColumns() {
+            return new JComponent[] {
+                    rowTitle(),
+                    mineField(),
+                    mineButton(),
+                    merged(),
+                    theirsButton(),
+                    theirsField()
+            };
+        }
+
+        protected void addConstraints(GBC constraints, int columnIndex) {
+            constraints.anchor(GBC.CENTER);
+            constraints.fill = GBC.BOTH;
+            constraints.weight(0, 0);
+            constraints.insets(3, marginTop, 3, 0);
+            if (columnIndex == 1 || columnIndex == 3 || columnIndex == 5) {
+                // resize those rows
+                constraints.weightx = 1;
+            }
+        }
+    }
+
+    /**
+     * A row that does not contain the merge buttons. Fields in this row fill both the button and filed area.
+     */
+    protected static class MergeRowWithoutButton extends MergeRow {
+        @Override
+        protected JComponent[] getColumns() {
+            return new JComponent[] {
+                    rowTitle(),
+                    mineField(), // width: 2
+                    null,
+                    merged(),
+                    theirsField(), // width: 2
+                    null,
+            };
+        }
+
+        @Override
+        protected void addConstraints(GBC constraints, int columnIndex) {
+            super.addConstraints(constraints, columnIndex);
+
+            if (columnIndex == 1 || columnIndex == 4) {
+                constraints.gridwidth = 2;
+            }
+        }
+    }
+
+    /**
+     * The title for the rows (mine, merged, theirs)
+     */
+    protected static class TitleRow extends MergeRow {
+        public TitleRow() {
+            // allow access from subclasses
+        }
+
+        @Override
+        protected JComponent mineField() {
+            JLabel label = new JLabel(tr("My version (local dataset)"));
+            label.setToolTipText(tr("Properties in my dataset, i.e. the local dataset"));
+            label.setHorizontalAlignment(JLabel.CENTER);
+            return label;
+        }
+
+        @Override
+        protected JComponent merged() {
+            JLabel label = new JLabel(tr("Merged version"));
+            label.setToolTipText(
+                    tr("Properties in the merged element. They will replace properties in my elements when merge decisions are applied."));
+            label.setHorizontalAlignment(JLabel.CENTER);
+            return label;
+        }
+
+        @Override
+        protected JComponent theirsField() {
+            JLabel label = new JLabel(tr("Their version (server dataset)"));
+            label.setToolTipText(tr("Properties in their dataset, i.e. the server dataset"));
+            label.setHorizontalAlignment(JLabel.CENTER);
+            return label;
+        }
+    }
+
+    protected int currentRow = 0;
+
+    /**
+     * Create a new merge panel.
+     */
+    public AbstractMergePanel() {
+        super(new GridBagLayout());
+    }
+
+    /**
+     * Add the rows to this component.
+     * This needs to be called in the constructor of the child class. That way, all it's fields are initialized.
+     */
+    protected void buildRows() {
+        getRows().forEach(row -> row.addTo(this));
+    }
+
+    /**
+     * Gets the rows.
+     * @return A list of rows that should be displayed in this dialog.
+     */
+    protected abstract List<? extends MergeRow> getRows();
+
+}
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMerger.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMerger.java	(revision 12043)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMerger.java	(revision 12044)
@@ -4,9 +4,7 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
-import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.text.DecimalFormat;
+import java.util.Arrays;
 import java.util.List;
 
@@ -15,4 +13,5 @@
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
+import javax.swing.JComponent;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
@@ -25,7 +24,9 @@
 import org.openstreetmap.josm.gui.DefaultNameFormatter;
 import org.openstreetmap.josm.gui.conflict.ConflictColors;
+import org.openstreetmap.josm.gui.conflict.pair.AbstractMergePanel;
 import org.openstreetmap.josm.gui.conflict.pair.IConflictResolver;
 import org.openstreetmap.josm.gui.conflict.pair.MergeDecisionType;
 import org.openstreetmap.josm.gui.history.VersionInfoPanel;
+import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Utils;
@@ -35,5 +36,5 @@
  * @since 1654
  */
-public class PropertiesMerger extends JPanel implements ChangeListener, IConflictResolver {
+public class PropertiesMerger extends AbstractMergePanel implements ChangeListener, IConflictResolver {
     private static final DecimalFormat COORD_FORMATTER = new DecimalFormat("###0.0000000");
 
@@ -49,5 +50,5 @@
     private final JLabel lblTheirReferrers = buildValueLabel("label.theirreferrers");
 
-    private final transient PropertiesMergeModel model;
+    private final transient PropertiesMergeModel model = new PropertiesMergeModel();
     private final VersionInfoPanel mineVersionInfo = new VersionInfoPanel();
     private final VersionInfoPanel theirVersionInfo = new VersionInfoPanel();
@@ -57,7 +58,19 @@
      */
     public PropertiesMerger() {
-        model = new PropertiesMergeModel();
         model.addChangeListener(this);
-        build();
+        buildRows();
+    }
+
+    @Override
+    protected List<? extends MergeRow> getRows() {
+        return Arrays.asList(
+                new AbstractMergePanel.TitleRow(),
+                new VersionInfoRow(),
+                new MergeCoordinatesRow(),
+                new UndecideCoordinatesRow(),
+                new MergeDeletedStateRow(),
+                new UndecideDeletedStateRow(),
+                new ReferrersRow(),
+                new EmptyFillRow());
     }
 
@@ -69,223 +82,4 @@
         lbl.setBorder(BorderFactory.createLoweredBevelBorder());
         return lbl;
-    }
-
-    protected void buildHeaderRow() {
-        GridBagConstraints gc = new GridBagConstraints();
-
-        gc.gridx = 1;
-        gc.gridy = 0;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        gc.insets = new Insets(10, 0, 0, 0);
-        JLabel lblMyVersion = new JLabel(tr("My version"));
-        lblMyVersion.setToolTipText(tr("Properties in my dataset, i.e. the local dataset"));
-        lblMyVersion.setLabelFor(mineVersionInfo);
-        add(lblMyVersion, gc);
-
-        gc.gridx = 3;
-        JLabel lblMergedVersion = new JLabel(tr("Merged version"));
-        lblMergedVersion.setToolTipText(
-                tr("Properties in the merged element. They will replace properties in my elements when merge decisions are applied."));
-        add(lblMergedVersion, gc);
-
-        gc.gridx = 5;
-        JLabel lblTheirVersion = new JLabel(tr("Their version"));
-        lblTheirVersion.setToolTipText(tr("Properties in their dataset, i.e. the server dataset"));
-        lblMyVersion.setLabelFor(theirVersionInfo);
-        add(lblTheirVersion, gc);
-
-        gc.gridx = 1;
-        gc.gridy = 1;
-        gc.fill = GridBagConstraints.HORIZONTAL;
-        gc.anchor = GridBagConstraints.LINE_START;
-        gc.insets = new Insets(0, 0, 20, 0);
-        add(mineVersionInfo, gc);
-
-        gc.gridx = 5;
-        add(theirVersionInfo, gc);
-    }
-
-    protected void buildCoordinateConflictRows() {
-        GridBagConstraints gc = new GridBagConstraints();
-
-        gc.gridx = 0;
-        gc.gridy = 2;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.HORIZONTAL;
-        gc.anchor = GridBagConstraints.LINE_START;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        gc.insets = new Insets(0, 5, 0, 5);
-        add(new JLabel(tr("Coordinates:")), gc);
-
-        gc.gridx = 1;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.33;
-        gc.weighty = 0.0;
-        add(lblMyCoordinates, gc);
-
-        gc.gridx = 2;
-        gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        KeepMyCoordinatesAction actKeepMyCoordinates = new KeepMyCoordinatesAction();
-        model.addChangeListener(actKeepMyCoordinates);
-        JButton btnKeepMyCoordinates = new JButton(actKeepMyCoordinates);
-        btnKeepMyCoordinates.setName("button.keepmycoordinates");
-        add(btnKeepMyCoordinates, gc);
-
-        gc.gridx = 3;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.33;
-        gc.weighty = 0.0;
-        add(lblMergedCoordinates, gc);
-
-        gc.gridx = 4;
-        gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        KeepTheirCoordinatesAction actKeepTheirCoordinates = new KeepTheirCoordinatesAction();
-        model.addChangeListener(actKeepTheirCoordinates);
-        JButton btnKeepTheirCoordinates = new JButton(actKeepTheirCoordinates);
-        add(btnKeepTheirCoordinates, gc);
-
-        gc.gridx = 5;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.33;
-        gc.weighty = 0.0;
-        add(lblTheirCoordinates, gc);
-
-        // ---------------------------------------------------
-        gc.gridx = 3;
-        gc.gridy = 3;
-        gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        UndecideCoordinateConflictAction actUndecideCoordinates = new UndecideCoordinateConflictAction();
-        model.addChangeListener(actUndecideCoordinates);
-        JButton btnUndecideCoordinates = new JButton(actUndecideCoordinates);
-        add(btnUndecideCoordinates, gc);
-    }
-
-    protected void buildDeletedStateConflictRows() {
-        GridBagConstraints gc = new GridBagConstraints();
-
-        gc.gridx = 0;
-        gc.gridy = 4;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.LINE_START;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        gc.insets = new Insets(0, 5, 0, 5);
-        add(new JLabel(tr("Deleted State:")), gc);
-
-        gc.gridx = 1;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.33;
-        gc.weighty = 0.0;
-        add(lblMyDeletedState, gc);
-
-        gc.gridx = 2;
-        gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        KeepMyDeletedStateAction actKeepMyDeletedState = new KeepMyDeletedStateAction();
-        model.addChangeListener(actKeepMyDeletedState);
-        JButton btnKeepMyDeletedState = new JButton(actKeepMyDeletedState);
-        btnKeepMyDeletedState.setName("button.keepmydeletedstate");
-        add(btnKeepMyDeletedState, gc);
-
-        gc.gridx = 3;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.33;
-        gc.weighty = 0.0;
-        add(lblMergedDeletedState, gc);
-
-        gc.gridx = 4;
-        gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        KeepTheirDeletedStateAction actKeepTheirDeletedState = new KeepTheirDeletedStateAction();
-        model.addChangeListener(actKeepTheirDeletedState);
-        JButton btnKeepTheirDeletedState = new JButton(actKeepTheirDeletedState);
-        btnKeepTheirDeletedState.setName("button.keeptheirdeletedstate");
-        add(btnKeepTheirDeletedState, gc);
-
-        gc.gridx = 5;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.33;
-        gc.weighty = 0.0;
-        add(lblTheirDeletedState, gc);
-
-        // ---------------------------------------------------
-        gc.gridx = 3;
-        gc.gridy = 5;
-        gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        UndecideDeletedStateConflictAction actUndecideDeletedState = new UndecideDeletedStateConflictAction();
-        model.addChangeListener(actUndecideDeletedState);
-        JButton btnUndecideDeletedState = new JButton(actUndecideDeletedState);
-        btnUndecideDeletedState.setName("button.undecidedeletedstate");
-        add(btnUndecideDeletedState, gc);
-    }
-
-    protected void buildReferrersRow() {
-        GridBagConstraints gc = new GridBagConstraints();
-
-        gc.gridx = 0;
-        gc.gridy = 7;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.LINE_START;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        gc.insets = new Insets(0, 5, 0, 5);
-        add(new JLabel(tr("Referenced by:")), gc);
-
-        gc.gridx = 1;
-        gc.gridy = 7;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.33;
-        gc.weighty = 0.0;
-        add(lblMyReferrers, gc);
-
-        gc.gridx = 5;
-        gc.gridy = 7;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.33;
-        gc.weighty = 0.0;
-        add(lblTheirReferrers, gc);
-    }
-
-    protected final void build() {
-        setLayout(new GridBagLayout());
-        buildHeaderRow();
-        buildCoordinateConflictRows();
-        buildDeletedStateConflictRows();
-        buildReferrersRow();
     }
 
@@ -399,4 +193,173 @@
     }
 
+    private final class MergeDeletedStateRow extends AbstractMergePanel.MergeRow {
+        @Override
+        protected JComponent rowTitle() {
+            return new JLabel(tr("Deleted State:"));
+        }
+
+        @Override
+        protected JComponent mineField() {
+            return lblMyDeletedState;
+        }
+
+        @Override
+        protected JComponent mineButton() {
+            KeepMyDeletedStateAction actKeepMyDeletedState = new KeepMyDeletedStateAction();
+            model.addChangeListener(actKeepMyDeletedState);
+            JButton btnKeepMyDeletedState = new JButton(actKeepMyDeletedState);
+            btnKeepMyDeletedState.setName("button.keepmydeletedstate");
+            return btnKeepMyDeletedState;
+        }
+
+        @Override
+        protected JComponent merged() {
+            return lblMergedDeletedState;
+        }
+
+        @Override
+        protected JComponent theirsButton() {
+            KeepTheirDeletedStateAction actKeepTheirDeletedState = new KeepTheirDeletedStateAction();
+            model.addChangeListener(actKeepTheirDeletedState);
+            JButton btnKeepTheirDeletedState = new JButton(actKeepTheirDeletedState);
+            btnKeepTheirDeletedState.setName("button.keeptheirdeletedstate");
+            return btnKeepTheirDeletedState;
+        }
+
+        @Override
+        protected JComponent theirsField() {
+            return lblTheirDeletedState;
+        }
+    }
+
+    private final class MergeCoordinatesRow extends AbstractMergePanel.MergeRow {
+        @Override
+        protected JComponent rowTitle() {
+            return new JLabel(tr("Coordinates:"));
+        }
+
+        @Override
+        protected JComponent mineField() {
+            return lblMyCoordinates;
+        }
+
+        @Override
+        protected JComponent mineButton() {
+            KeepMyCoordinatesAction actKeepMyCoordinates = new KeepMyCoordinatesAction();
+            model.addChangeListener(actKeepMyCoordinates);
+            JButton btnKeepMyCoordinates = new JButton(actKeepMyCoordinates);
+            btnKeepMyCoordinates.setName("button.keepmycoordinates");
+            return btnKeepMyCoordinates;
+        }
+
+        @Override
+        protected JComponent merged() {
+            return lblMergedCoordinates;
+        }
+
+        @Override
+        protected JComponent theirsButton() {
+            KeepTheirCoordinatesAction actKeepTheirCoordinates = new KeepTheirCoordinatesAction();
+            model.addChangeListener(actKeepTheirCoordinates);
+            JButton btnKeepTheirCoordinates = new JButton(actKeepTheirCoordinates);
+            btnKeepTheirCoordinates.setName("button.keeptheircoordinates");
+            return btnKeepTheirCoordinates;
+        }
+
+        @Override
+        protected JComponent theirsField() {
+            return lblTheirCoordinates;
+        }
+    }
+
+    private abstract class AbstractUndecideRow<T extends AbstractAction & ChangeListener> extends AbstractMergePanel.MergeRow {
+        @Override
+        protected JComponent merged() {
+            // we add the undecide button below this text field
+            T actUndecideCoordinates = createAction();
+            model.addChangeListener(actUndecideCoordinates);
+            JButton button = new JButton(actUndecideCoordinates);
+            button.setName(getButtonName());
+            return button;
+        }
+
+        protected abstract T createAction();
+
+        protected abstract String getButtonName();
+
+        @Override
+        protected void addConstraints(GBC constraints, int columnIndex) {
+            super.addConstraints(constraints, columnIndex);
+            constraints.fill(GBC.NONE);
+        }
+    }
+
+    private final class UndecideCoordinatesRow extends AbstractUndecideRow<UndecideCoordinateConflictAction> {
+        @Override
+        protected UndecideCoordinateConflictAction createAction() {
+            return new UndecideCoordinateConflictAction();
+        }
+
+        @Override
+        protected String getButtonName() {
+            return "button.undecidecoordinates";
+        }
+    }
+
+    private final class UndecideDeletedStateRow extends AbstractUndecideRow<UndecideDeletedStateConflictAction> {
+        @Override
+        protected UndecideDeletedStateConflictAction createAction() {
+            return new UndecideDeletedStateConflictAction();
+        }
+
+        @Override
+        protected String getButtonName() {
+            return "button.undecidedeletedstate";
+        }
+    }
+
+    private final class VersionInfoRow extends AbstractMergePanel.MergeRowWithoutButton {
+        @Override
+        protected JComponent mineField() {
+            return mineVersionInfo;
+        }
+
+        @Override
+        protected JComponent theirsField() {
+            return theirVersionInfo;
+        }
+    }
+
+    private final class ReferrersRow extends AbstractMergePanel.MergeRow {
+        @Override
+        protected JComponent rowTitle() {
+            return new JLabel(tr("Referenced by:"));
+        }
+
+        @Override
+        protected JComponent mineField() {
+            return lblMyReferrers;
+        }
+
+        @Override
+        protected JComponent theirsField() {
+            return lblTheirReferrers;
+        }
+    }
+
+    private final class EmptyFillRow extends AbstractMergePanel.MergeRow {
+        @Override
+        protected JComponent merged() {
+            return new JPanel();
+        }
+
+        @Override
+        protected void addConstraints(GBC constraints, int columnIndex) {
+            super.addConstraints(constraints, columnIndex);
+            // fill to bottom
+            constraints.weighty = 1;
+        }
+    }
+
     class KeepMyCoordinatesAction extends AbstractAction implements ChangeListener {
         KeepMyCoordinatesAction() {
