// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.conflict.pair; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trn; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collection; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JToggleButton; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.command.conflict.ConflictResolveCommand; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.PrimitiveId; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.gui.util.AdjustmentSynchronizer; import org.openstreetmap.josm.gui.widgets.JosmComboBox; import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTable; import org.openstreetmap.josm.tools.ImageProvider; /** * A UI component for resolving conflicts in two lists of entries of type T. * * @param <T> the type of the entries * @param <C> the type of conflict resolution command * @see AbstractListMergeModel * @since 1631 */ public abstract class AbstractListMerger<T extends PrimitiveId, C extends ConflictResolveCommand> extends JPanel implements PropertyChangeListener, ChangeListener, IConflictResolver { protected OsmPrimitivesTable myEntriesTable; protected OsmPrimitivesTable mergedEntriesTable; protected OsmPrimitivesTable theirEntriesTable; protected transient AbstractListMergeModel<T, C> model; private CopyStartLeftAction copyStartLeftAction; private CopyBeforeCurrentLeftAction copyBeforeCurrentLeftAction; private CopyAfterCurrentLeftAction copyAfterCurrentLeftAction; private CopyEndLeftAction copyEndLeftAction; private CopyAllLeft copyAllLeft; private CopyStartRightAction copyStartRightAction; private CopyBeforeCurrentRightAction copyBeforeCurrentRightAction; private CopyAfterCurrentRightAction copyAfterCurrentRightAction; private CopyEndRightAction copyEndRightAction; private CopyAllRight copyAllRight; private MoveUpMergedAction moveUpMergedAction; private MoveDownMergedAction moveDownMergedAction; private RemoveMergedAction removeMergedAction; private FreezeAction freezeAction; private transient AdjustmentSynchronizer adjustmentSynchronizer; private JLabel lblMyVersion; private JLabel lblMergedVersion; private JLabel lblTheirVersion; private JLabel lblFrozenState; protected abstract JScrollPane buildMyElementsTable(); protected abstract JScrollPane buildMergedElementsTable(); protected abstract JScrollPane buildTheirElementsTable(); protected JScrollPane embeddInScrollPane(JTable table) { JScrollPane pane = new JScrollPane(table); if (adjustmentSynchronizer == null) { adjustmentSynchronizer = new AdjustmentSynchronizer(); } return pane; } protected void wireActionsToSelectionModels() { myEntriesTable.getSelectionModel().addListSelectionListener(copyStartLeftAction); myEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction); mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction); myEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction); mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction); myEntriesTable.getSelectionModel().addListSelectionListener(copyEndLeftAction); theirEntriesTable.getSelectionModel().addListSelectionListener(copyStartRightAction); theirEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction); mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction); theirEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction); mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction); theirEntriesTable.getSelectionModel().addListSelectionListener(copyEndRightAction); mergedEntriesTable.getSelectionModel().addListSelectionListener(moveUpMergedAction); mergedEntriesTable.getSelectionModel().addListSelectionListener(moveDownMergedAction); mergedEntriesTable.getSelectionModel().addListSelectionListener(removeMergedAction); model.addChangeListener(copyAllLeft); model.addChangeListener(copyAllRight); model.addPropertyChangeListener(copyAllLeft); model.addPropertyChangeListener(copyAllRight); } protected JPanel buildLeftButtonPanel() { JPanel pnl = new JPanel(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); gc.gridx = 0; gc.gridy = 0; copyStartLeftAction = new CopyStartLeftAction(); JButton btn = new JButton(copyStartLeftAction); btn.setName("button.copystartleft"); pnl.add(btn, gc); gc.gridx = 0; gc.gridy = 1; copyBeforeCurrentLeftAction = new CopyBeforeCurrentLeftAction(); btn = new JButton(copyBeforeCurrentLeftAction); btn.setName("button.copybeforecurrentleft"); pnl.add(btn, gc); gc.gridx = 0; gc.gridy = 2; copyAfterCurrentLeftAction = new CopyAfterCurrentLeftAction(); btn = new JButton(copyAfterCurrentLeftAction); btn.setName("button.copyaftercurrentleft"); pnl.add(btn, gc); gc.gridx = 0; gc.gridy = 3; copyEndLeftAction = new CopyEndLeftAction(); btn = new JButton(copyEndLeftAction); btn.setName("button.copyendleft"); pnl.add(btn, gc); gc.gridx = 0; gc.gridy = 4; copyAllLeft = new CopyAllLeft(); btn = new JButton(copyAllLeft); btn.setName("button.copyallleft"); pnl.add(btn, gc); return pnl; } protected JPanel buildRightButtonPanel() { JPanel pnl = new JPanel(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); gc.gridx = 0; gc.gridy = 0; copyStartRightAction = new CopyStartRightAction(); pnl.add(new JButton(copyStartRightAction), gc); gc.gridx = 0; gc.gridy = 1; copyBeforeCurrentRightAction = new CopyBeforeCurrentRightAction(); pnl.add(new JButton(copyBeforeCurrentRightAction), gc); gc.gridx = 0; gc.gridy = 2; copyAfterCurrentRightAction = new CopyAfterCurrentRightAction(); pnl.add(new JButton(copyAfterCurrentRightAction), gc); gc.gridx = 0; gc.gridy = 3; copyEndRightAction = new CopyEndRightAction(); pnl.add(new JButton(copyEndRightAction), gc); gc.gridx = 0; gc.gridy = 4; copyAllRight = new CopyAllRight(); pnl.add(new JButton(copyAllRight), gc); return pnl; } protected JPanel buildMergedListControlButtons() { JPanel pnl = new JPanel(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); gc.gridx = 0; gc.gridy = 0; gc.gridwidth = 1; gc.gridheight = 1; gc.fill = GridBagConstraints.HORIZONTAL; gc.anchor = GridBagConstraints.CENTER; gc.weightx = 0.3; gc.weighty = 0.0; moveUpMergedAction = new MoveUpMergedAction(); pnl.add(new JButton(moveUpMergedAction), gc); gc.gridx = 1; gc.gridy = 0; moveDownMergedAction = new MoveDownMergedAction(); pnl.add(new JButton(moveDownMergedAction), gc); gc.gridx = 2; gc.gridy = 0; removeMergedAction = new RemoveMergedAction(); pnl.add(new JButton(removeMergedAction), gc); return pnl; } protected JPanel buildAdjustmentLockControlPanel(JCheckBox cb) { JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); panel.add(new JLabel(tr("lock scrolling"))); panel.add(cb); return panel; } protected JPanel buildComparePairSelectionPanel() { JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT)); p.add(new JLabel(tr("Compare "))); JosmComboBox<ComparePairType> cbComparePair = new JosmComboBox<>(model.getComparePairListModel()); cbComparePair.setRenderer(new ComparePairListCellRenderer()); p.add(cbComparePair); return p; } protected JPanel buildFrozeStateControlPanel() { JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT)); lblFrozenState = new JLabel(); p.add(lblFrozenState); freezeAction = new FreezeAction(); JToggleButton btn = new JToggleButton(freezeAction); freezeAction.adapt(btn); btn.setName("button.freeze"); p.add(btn); return p; } protected final void build() { setLayout(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); // ------------------ gc.gridx = 0; 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); lblMyVersion = new JLabel(tr("My version")); lblMyVersion.setToolTipText(tr("List of elements in my dataset, i.e. the local dataset")); add(lblMyVersion, gc); gc.gridx = 2; gc.gridy = 0; lblMergedVersion = new JLabel(tr("Merged version")); lblMergedVersion.setToolTipText( tr("List of merged elements. They will replace the list of my elements when the merge decisions are applied.")); add(lblMergedVersion, gc); gc.gridx = 4; gc.gridy = 0; lblTheirVersion = new JLabel(tr("Their version")); lblTheirVersion.setToolTipText(tr("List of elements in their dataset, i.e. the server dataset")); add(lblTheirVersion, gc); // ------------------------------ gc.gridx = 0; gc.gridy = 1; gc.gridwidth = 1; gc.gridheight = 1; gc.fill = GridBagConstraints.HORIZONTAL; gc.anchor = GridBagConstraints.FIRST_LINE_START; gc.weightx = 0.33; gc.weighty = 0.0; gc.insets = new Insets(0, 0, 0, 0); JCheckBox cbLockMyScrolling = new JCheckBox(); cbLockMyScrolling.setName("checkbox.lockmyscrolling"); add(buildAdjustmentLockControlPanel(cbLockMyScrolling), gc); gc.gridx = 2; gc.gridy = 1; JCheckBox cbLockMergedScrolling = new JCheckBox(); cbLockMergedScrolling.setName("checkbox.lockmergedscrolling"); add(buildAdjustmentLockControlPanel(cbLockMergedScrolling), gc); gc.gridx = 4; gc.gridy = 1; JCheckBox cbLockTheirScrolling = new JCheckBox(); cbLockTheirScrolling.setName("checkbox.locktheirscrolling"); add(buildAdjustmentLockControlPanel(cbLockTheirScrolling), gc); // -------------------------------- gc.gridx = 0; gc.gridy = 2; gc.gridwidth = 1; gc.gridheight = 1; gc.fill = GridBagConstraints.BOTH; gc.anchor = GridBagConstraints.FIRST_LINE_START; gc.weightx = 0.33; gc.weighty = 1.0; gc.insets = new Insets(0, 0, 0, 0); JScrollPane pane = buildMyElementsTable(); lblMyVersion.setLabelFor(pane); adjustmentSynchronizer.adapt(cbLockMyScrolling, pane.getVerticalScrollBar()); add(pane, gc); gc.gridx = 1; gc.gridy = 2; gc.fill = GridBagConstraints.NONE; gc.anchor = GridBagConstraints.CENTER; gc.weightx = 0.0; gc.weighty = 0.0; add(buildLeftButtonPanel(), gc); gc.gridx = 2; gc.gridy = 2; gc.fill = GridBagConstraints.BOTH; gc.anchor = GridBagConstraints.FIRST_LINE_START; gc.weightx = 0.33; gc.weighty = 0.0; pane = buildMergedElementsTable(); lblMergedVersion.setLabelFor(pane); adjustmentSynchronizer.adapt(cbLockMergedScrolling, pane.getVerticalScrollBar()); add(pane, gc); gc.gridx = 3; gc.gridy = 2; gc.fill = GridBagConstraints.NONE; gc.anchor = GridBagConstraints.CENTER; gc.weightx = 0.0; gc.weighty = 0.0; add(buildRightButtonPanel(), gc); gc.gridx = 4; gc.gridy = 2; gc.fill = GridBagConstraints.BOTH; gc.anchor = GridBagConstraints.FIRST_LINE_START; gc.weightx = 0.33; gc.weighty = 0.0; pane = buildTheirElementsTable(); lblTheirVersion.setLabelFor(pane); adjustmentSynchronizer.adapt(cbLockTheirScrolling, pane.getVerticalScrollBar()); add(pane, gc); // ---------------------------------- gc.gridx = 2; gc.gridy = 3; gc.gridwidth = 1; gc.gridheight = 1; gc.fill = GridBagConstraints.BOTH; gc.anchor = GridBagConstraints.CENTER; gc.weightx = 0.0; gc.weighty = 0.0; add(buildMergedListControlButtons(), gc); // ----------------------------------- gc.gridx = 0; gc.gridy = 4; gc.gridwidth = 2; gc.gridheight = 1; gc.fill = GridBagConstraints.HORIZONTAL; gc.anchor = GridBagConstraints.LINE_START; gc.weightx = 0.0; gc.weighty = 0.0; add(buildComparePairSelectionPanel(), gc); gc.gridx = 2; gc.gridy = 4; gc.gridwidth = 3; gc.gridheight = 1; gc.fill = GridBagConstraints.HORIZONTAL; gc.anchor = GridBagConstraints.LINE_START; gc.weightx = 0.0; gc.weighty = 0.0; add(buildFrozeStateControlPanel(), gc); wireActionsToSelectionModels(); } /** * Constructs a new {@code ListMerger}. * @param model list merger model */ public AbstractListMerger(AbstractListMergeModel<T, C> model) { this.model = model; model.addChangeListener(this); build(); model.addPropertyChangeListener(this); } /** * Base class of all other Copy* inner classes. */ abstract static class CopyAction extends AbstractAction implements ListSelectionListener { protected CopyAction(String iconName, String actionName, String shortDescription) { ImageIcon icon = ImageProvider.get("dialogs/conflict", iconName); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, actionName); } putValue(Action.SHORT_DESCRIPTION, shortDescription); setEnabled(false); } } /** * Action for copying selected nodes in the list of my nodes to the list of merged * nodes. Inserts the nodes at the beginning of the list of merged nodes. */ class CopyStartLeftAction extends CopyAction { CopyStartLeftAction() { super(/* ICON(dialogs/conflict/)*/ "copystartleft", tr("> top"), tr("Copy my selected nodes to the start of the merged node list")); } @Override public void actionPerformed(ActionEvent e) { model.copyMyToTop(myEntriesTable.getSelectedRows()); } @Override public void valueChanged(ListSelectionEvent e) { setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty()); } } /** * Action for copying selected nodes in the list of my nodes to the list of merged * nodes. Inserts the nodes at the end of the list of merged nodes. */ class CopyEndLeftAction extends CopyAction { CopyEndLeftAction() { super(/* ICON(dialogs/conflict/)*/ "copyendleft", tr("> bottom"), tr("Copy my selected elements to the end of the list of merged elements.")); } @Override public void actionPerformed(ActionEvent e) { model.copyMyToEnd(myEntriesTable.getSelectedRows()); } @Override public void valueChanged(ListSelectionEvent e) { setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty()); } } /** * Action for copying selected nodes in the list of my nodes to the list of merged * nodes. Inserts the nodes before the first selected row in the list of merged nodes. */ class CopyBeforeCurrentLeftAction extends CopyAction { CopyBeforeCurrentLeftAction() { super(/* ICON(dialogs/conflict/)*/ "copybeforecurrentleft", tr("> before"), tr("Copy my selected elements before the first selected element in the list of merged elements.")); } @Override public void actionPerformed(ActionEvent e) { int[] mergedRows = mergedEntriesTable.getSelectedRows(); if (mergedRows.length == 0) return; int[] myRows = myEntriesTable.getSelectedRows(); int current = mergedRows[0]; model.copyMyBeforeCurrent(myRows, current); } @Override public void valueChanged(ListSelectionEvent e) { setEnabled( !myEntriesTable.getSelectionModel().isSelectionEmpty() && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() ); } } /** * Action for copying selected nodes in the list of my nodes to the list of merged * nodes. Inserts the nodes after the first selected row in the list of merged nodes. */ class CopyAfterCurrentLeftAction extends CopyAction { CopyAfterCurrentLeftAction() { super(/* ICON(dialogs/conflict/)*/ "copyaftercurrentleft", tr("> after"), tr("Copy my selected elements after the first selected element in the list of merged elements.")); } @Override public void actionPerformed(ActionEvent e) { int[] mergedRows = mergedEntriesTable.getSelectedRows(); if (mergedRows.length == 0) return; int[] myRows = myEntriesTable.getSelectedRows(); int current = mergedRows[0]; model.copyMyAfterCurrent(myRows, current); } @Override public void valueChanged(ListSelectionEvent e) { setEnabled( !myEntriesTable.getSelectionModel().isSelectionEmpty() && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() ); } } class CopyStartRightAction extends CopyAction { CopyStartRightAction() { super(/* ICON(dialogs/conflict/)*/ "copystartright", tr("< top"), tr("Copy their selected element to the start of the list of merged elements.")); } @Override public void actionPerformed(ActionEvent e) { model.copyTheirToTop(theirEntriesTable.getSelectedRows()); } @Override public void valueChanged(ListSelectionEvent e) { setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); } } class CopyEndRightAction extends CopyAction { CopyEndRightAction() { super(/* ICON(dialogs/conflict/)*/ "copyendright", tr("< bottom"), tr("Copy their selected elements to the end of the list of merged elements.")); } @Override public void actionPerformed(ActionEvent arg0) { model.copyTheirToEnd(theirEntriesTable.getSelectedRows()); } @Override public void valueChanged(ListSelectionEvent e) { setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); } } class CopyBeforeCurrentRightAction extends CopyAction { CopyBeforeCurrentRightAction() { super(/* ICON(dialogs/conflict/)*/ "copybeforecurrentright", tr("< before"), tr("Copy their selected elements before the first selected element in the list of merged elements.")); } @Override public void actionPerformed(ActionEvent e) { int[] mergedRows = mergedEntriesTable.getSelectedRows(); if (mergedRows.length == 0) return; int[] myRows = theirEntriesTable.getSelectedRows(); int current = mergedRows[0]; model.copyTheirBeforeCurrent(myRows, current); } @Override public void valueChanged(ListSelectionEvent e) { setEnabled( !theirEntriesTable.getSelectionModel().isSelectionEmpty() && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() ); } } class CopyAfterCurrentRightAction extends CopyAction { CopyAfterCurrentRightAction() { super(/* ICON(dialogs/conflict/)*/ "copyaftercurrentright", tr("< after"), tr("Copy their selected element after the first selected element in the list of merged elements")); } @Override public void actionPerformed(ActionEvent e) { int[] mergedRows = mergedEntriesTable.getSelectedRows(); if (mergedRows.length == 0) return; int[] myRows = theirEntriesTable.getSelectedRows(); int current = mergedRows[0]; model.copyTheirAfterCurrent(myRows, current); } @Override public void valueChanged(ListSelectionEvent e) { setEnabled( !theirEntriesTable.getSelectionModel().isSelectionEmpty() && !mergedEntriesTable.getSelectionModel().isSelectionEmpty() ); } } class CopyAllLeft extends AbstractAction implements ChangeListener, PropertyChangeListener { CopyAllLeft() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallleft"); putValue(Action.SMALL_ICON, icon); putValue(Action.SHORT_DESCRIPTION, tr("Copy all my elements to the target")); } @Override public void actionPerformed(ActionEvent arg0) { model.copyAll(ListRole.MY_ENTRIES); model.setFrozen(true); } private void updateEnabledState() { setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); } @Override public void stateChanged(ChangeEvent e) { updateEnabledState(); } @Override public void propertyChange(PropertyChangeEvent evt) { updateEnabledState(); } } class CopyAllRight extends AbstractAction implements ChangeListener, PropertyChangeListener { CopyAllRight() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallright"); putValue(Action.SMALL_ICON, icon); putValue(Action.SHORT_DESCRIPTION, tr("Copy all their elements to the target")); } @Override public void actionPerformed(ActionEvent arg0) { model.copyAll(ListRole.THEIR_ENTRIES); model.setFrozen(true); } private void updateEnabledState() { setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); } @Override public void stateChanged(ChangeEvent e) { updateEnabledState(); } @Override public void propertyChange(PropertyChangeEvent evt) { updateEnabledState(); } } class MoveUpMergedAction extends AbstractAction implements ListSelectionListener { MoveUpMergedAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "moveup"); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, tr("Up")); } putValue(Action.SHORT_DESCRIPTION, tr("Move up the selected entries by one position.")); setEnabled(false); } @Override public void actionPerformed(ActionEvent arg0) { int[] rows = mergedEntriesTable.getSelectedRows(); model.moveUpMerged(rows); } @Override public void valueChanged(ListSelectionEvent e) { int[] rows = mergedEntriesTable.getSelectedRows(); setEnabled(rows.length > 0 && rows[0] != 0 ); } } /** * Action for moving the currently selected entries in the list of merged entries * one position down * */ class MoveDownMergedAction extends AbstractAction implements ListSelectionListener { MoveDownMergedAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "movedown"); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, tr("Down")); } putValue(Action.SHORT_DESCRIPTION, tr("Move down the selected entries by one position.")); setEnabled(false); } @Override public void actionPerformed(ActionEvent arg0) { int[] rows = mergedEntriesTable.getSelectedRows(); model.moveDownMerged(rows); } @Override public void valueChanged(ListSelectionEvent e) { int[] rows = mergedEntriesTable.getSelectedRows(); setEnabled(rows.length > 0 && rows[rows.length -1] != mergedEntriesTable.getRowCount() -1 ); } } /** * Action for removing the selected entries in the list of merged entries * from the list of merged entries. * */ class RemoveMergedAction extends AbstractAction implements ListSelectionListener { RemoveMergedAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "remove"); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, tr("Remove")); } putValue(Action.SHORT_DESCRIPTION, tr("Remove the selected entries from the list of merged elements.")); setEnabled(false); } @Override public void actionPerformed(ActionEvent arg0) { int[] rows = mergedEntriesTable.getSelectedRows(); model.removeMerged(rows); } @Override public void valueChanged(ListSelectionEvent e) { int[] rows = mergedEntriesTable.getSelectedRows(); setEnabled(rows.length > 0); } } private interface FreezeActionProperties { String PROP_SELECTED = FreezeActionProperties.class.getName() + ".selected"; } /** * Action for freezing the current state of the list merger * */ private final class FreezeAction extends AbstractAction implements ItemListener, FreezeActionProperties { private FreezeAction() { putValue(Action.NAME, tr("Freeze")); putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); putValue(PROP_SELECTED, Boolean.FALSE); setEnabled(true); } @Override public void actionPerformed(ActionEvent arg0) { // do nothing } /** * Java 1.5 doesn't known Action.SELECT_KEY. Wires a toggle button to this action * such that the action gets notified about item state changes and the button gets * notified about selection state changes of the action. * * @param btn a toggle button */ public void adapt(final JToggleButton btn) { btn.addItemListener(this); addPropertyChangeListener(evt -> { if (evt.getPropertyName().equals(PROP_SELECTED)) { btn.setSelected((Boolean) evt.getNewValue()); } }); } @Override public void itemStateChanged(ItemEvent e) { int state = e.getStateChange(); if (state == ItemEvent.SELECTED) { putValue(Action.NAME, tr("Unfreeze")); putValue(Action.SHORT_DESCRIPTION, tr("Unfreeze the list of merged elements and start merging.")); model.setFrozen(true); } else if (state == ItemEvent.DESELECTED) { putValue(Action.NAME, tr("Freeze")); putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); model.setFrozen(false); } boolean isSelected = (Boolean) getValue(PROP_SELECTED); if (isSelected != (e.getStateChange() == ItemEvent.SELECTED)) { putValue(PROP_SELECTED, e.getStateChange() == ItemEvent.SELECTED); } } } protected void handlePropertyChangeFrozen(boolean newValue) { myEntriesTable.getSelectionModel().clearSelection(); myEntriesTable.setEnabled(!newValue); theirEntriesTable.getSelectionModel().clearSelection(); theirEntriesTable.setEnabled(!newValue); mergedEntriesTable.getSelectionModel().clearSelection(); mergedEntriesTable.setEnabled(!newValue); freezeAction.putValue(FreezeActionProperties.PROP_SELECTED, newValue); if (newValue) { lblFrozenState.setText( tr("<html>Click <strong>{0}</strong> to start merging my and their entries.</html>", freezeAction.getValue(Action.NAME)) ); } else { lblFrozenState.setText( tr("<html>Click <strong>{0}</strong> to finish merging my and their entries.</html>", freezeAction.getValue(Action.NAME)) ); } } @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(AbstractListMergeModel.FROZEN_PROP)) { handlePropertyChangeFrozen((Boolean) evt.getNewValue()); } } /** * Returns the model. * @return the model */ public AbstractListMergeModel<T, C> getModel() { return model; } @Override public void stateChanged(ChangeEvent e) { lblMyVersion.setText( trn("My version ({0} entry)", "My version ({0} entries)", model.getMyEntriesSize(), model.getMyEntriesSize()) ); lblMergedVersion.setText( trn("Merged version ({0} entry)", "Merged version ({0} entries)", model.getMergedEntriesSize(), model.getMergedEntriesSize()) ); lblTheirVersion.setText( trn("Their version ({0} entry)", "Their version ({0} entries)", model.getTheirEntriesSize(), model.getTheirEntriesSize()) ); } /** * Adds all registered listeners by this merger * @see #unregisterListeners() * @since 10454 */ public void registerListeners() { myEntriesTable.registerListeners(); mergedEntriesTable.registerListeners(); theirEntriesTable.registerListeners(); } /** * Removes all registered listeners by this merger * @since 10454 */ public void unregisterListeners() { myEntriesTable.unregisterListeners(); mergedEntriesTable.unregisterListeners(); theirEntriesTable.unregisterListeners(); } protected final <P extends OsmPrimitive> OsmDataLayer findLayerFor(P primitive) { if (primitive != null) { Iterable<OsmDataLayer> layers = Main.getLayerManager().getLayersOfType(OsmDataLayer.class); // Find layer with same dataset for (OsmDataLayer layer : layers) { if (layer.data == primitive.getDataSet()) { return layer; } } // Conflict after merging layers: a dataset could be no more in any layer, try to find another layer with same primitive for (OsmDataLayer layer : layers) { final Collection<? extends OsmPrimitive> collection; if (primitive instanceof Way) { collection = layer.data.getWays(); } else if (primitive instanceof Relation) { collection = layer.data.getRelations(); } else { collection = layer.data.allPrimitives(); } for (OsmPrimitive p : collection) { if (p.getPrimitiveId().equals(primitive.getPrimitiveId())) { return layer; } } } } return null; } @Override public void decideRemaining(MergeDecisionType decision) { if (!model.isFrozen()) { model.copyAll(MergeDecisionType.KEEP_MINE.equals(decision) ? ListRole.MY_ENTRIES : ListRole.THEIR_ENTRIES); model.setFrozen(true); } } }