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.Adjustable; 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.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.HashMap; import java.util.Observable; import java.util.Observer; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JToggleButton; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.openstreetmap.josm.tools.CheckParameterUtil; 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 * @see ListMergeModel */ public abstract class ListMerger<T> extends JPanel implements PropertyChangeListener, Observer { //private static final Logger logger = Logger.getLogger(ListMerger.class.getName()); protected JTable myEntriesTable; protected JTable mergedEntriesTable; protected JTable theirEntriesTable; protected ListMergeModel<T> 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 AdjustmentSynchronizer adjustmentSynchronizer; private JCheckBox cbLockMyScrolling; private JCheckBox cbLockMergedScrolling; private JCheckBox cbLockTheirScrolling; private JLabel lblMyVersion; private JLabel lblMergedVersion; private JLabel lblTheirVersion; private JLabel lblFrozenState; abstract protected JScrollPane buildMyElementsTable(); abstract protected JScrollPane buildMergedElementsTable(); abstract protected JScrollPane buildTheirElementsTable(); protected JScrollPane embeddInScrollPane(JTable table) { JScrollPane pane = new JScrollPane(table); pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); 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.addObserver(copyAllLeft); model.addObserver(copyAllRight); model.addPropertyChangeListener(copyAllLeft); model.addPropertyChangeListener(copyAllRight); } protected JPanel buildLeftButtonPanel() { JPanel pnl = new JPanel(); pnl.setLayout(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(); pnl.setLayout(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(); pnl.setLayout(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(); panel.setLayout(new FlowLayout(FlowLayout.RIGHT)); panel.add(new JLabel(tr("lock scrolling"))); panel.add(cb); return panel; } protected JPanel buildComparePairSelectionPanel() { JPanel p = new JPanel(); p.setLayout(new FlowLayout(FlowLayout.LEFT)); p.add(new JLabel(tr("Compare "))); JComboBox cbComparePair =new JComboBox(model.getComparePairListModel()); cbComparePair.setRenderer(new ComparePairListCellRenderer()); p.add(cbComparePair); return p; } protected JPanel buildFrozeStateControlPanel() { JPanel p = new JPanel(); p.setLayout(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 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 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); cbLockMyScrolling = new JCheckBox(); cbLockMyScrolling.setName("checkbox.lockmyscrolling"); add(buildAdjustmentLockControlPanel(cbLockMyScrolling), gc); gc.gridx = 2; gc.gridy = 1; cbLockMergedScrolling = new JCheckBox(); cbLockMergedScrolling.setName("checkbox.lockmergedscrolling"); add(buildAdjustmentLockControlPanel(cbLockMergedScrolling), gc); gc.gridx = 4; gc.gridy = 1; 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(); 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(); 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(); 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(); } public ListMerger(ListMergeModel<T> model) { this.model = model; model.addObserver(this); build(); model.addPropertyChangeListener(this); } /** * 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 AbstractAction implements ListSelectionListener { public CopyStartLeftAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "copystartleft.png"); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, tr("> top")); } putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected nodes to the start of the merged node list")); setEnabled(false); } public void actionPerformed(ActionEvent arg0) { int [] rows = myEntriesTable.getSelectedRows(); model.copyMyToTop(rows); } 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 AbstractAction implements ListSelectionListener { public CopyEndLeftAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyendleft.png"); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, tr("> bottom")); } putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected elements to the end of the list of merged elements.")); setEnabled(false); } public void actionPerformed(ActionEvent arg0) { int [] rows = myEntriesTable.getSelectedRows(); model.copyMyToEnd(rows); } 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 AbstractAction implements ListSelectionListener { public CopyBeforeCurrentLeftAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "copybeforecurrentleft.png"); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, "> before"); } putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected elements before the first selected element in the list of merged elements.")); setEnabled(false); } public void actionPerformed(ActionEvent arg0) { int [] myRows = myEntriesTable.getSelectedRows(); int [] mergedRows = mergedEntriesTable.getSelectedRows(); if (mergedRows == null || mergedRows.length == 0) return; int current = mergedRows[0]; model.copyMyBeforeCurrent(myRows, current); } 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 AbstractAction implements ListSelectionListener { public CopyAfterCurrentLeftAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyaftercurrentleft.png"); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, "> after"); } putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected elements after the first selected element in the list of merged elements.")); setEnabled(false); } public void actionPerformed(ActionEvent arg0) { int [] myRows = myEntriesTable.getSelectedRows(); int [] mergedRows = mergedEntriesTable.getSelectedRows(); if (mergedRows == null || mergedRows.length == 0) return; int current = mergedRows[0]; model.copyMyAfterCurrent(myRows, current); } public void valueChanged(ListSelectionEvent e) { setEnabled( !myEntriesTable.getSelectionModel().isSelectionEmpty() && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty() ); } } class CopyStartRightAction extends AbstractAction implements ListSelectionListener { public CopyStartRightAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "copystartright.png"); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, "< top"); } putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected element to the start of the list of merged elements.")); setEnabled(false); } public void actionPerformed(ActionEvent arg0) { int [] rows = theirEntriesTable.getSelectedRows(); model.copyTheirToTop(rows); } public void valueChanged(ListSelectionEvent e) { setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); } } class CopyEndRightAction extends AbstractAction implements ListSelectionListener { public CopyEndRightAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyendright.png"); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, "< bottom"); } putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected elements to the end of the list of merged elements.")); setEnabled(false); } public void actionPerformed(ActionEvent arg0) { int [] rows = theirEntriesTable.getSelectedRows(); model.copyTheirToEnd(rows); } public void valueChanged(ListSelectionEvent e) { setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty()); } } class CopyBeforeCurrentRightAction extends AbstractAction implements ListSelectionListener { public CopyBeforeCurrentRightAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "copybeforecurrentright.png"); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, "< before"); } putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected elements before the first selected element in the list of merged elements.")); setEnabled(false); } public void actionPerformed(ActionEvent arg0) { int [] myRows = theirEntriesTable.getSelectedRows(); int [] mergedRows = mergedEntriesTable.getSelectedRows(); if (mergedRows == null || mergedRows.length == 0) return; int current = mergedRows[0]; model.copyTheirBeforeCurrent(myRows, current); } public void valueChanged(ListSelectionEvent e) { setEnabled( !theirEntriesTable.getSelectionModel().isSelectionEmpty() && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty() ); } } class CopyAfterCurrentRightAction extends AbstractAction implements ListSelectionListener { public CopyAfterCurrentRightAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyaftercurrentright.png"); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, "< after"); } putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected element after the first selected element in the list of merged elements")); setEnabled(false); } public void actionPerformed(ActionEvent arg0) { int [] myRows = theirEntriesTable.getSelectedRows(); int [] mergedRows = mergedEntriesTable.getSelectedRows(); if (mergedRows == null || mergedRows.length == 0) return; int current = mergedRows[0]; model.copyTheirAfterCurrent(myRows, current); } public void valueChanged(ListSelectionEvent e) { setEnabled( !theirEntriesTable.getSelectionModel().isSelectionEmpty() && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty() ); } } class CopyAllLeft extends AbstractAction implements Observer, PropertyChangeListener { public CopyAllLeft() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallleft.png"); putValue(Action.SMALL_ICON, icon); putValue(Action.SHORT_DESCRIPTION, tr("Copy all my elements to the target")); } public void actionPerformed(ActionEvent arg0) { model.copyAll(ListRole.MY_ENTRIES); model.setFrozen(true); } private void updateEnabledState() { setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); } public void update(Observable o, Object arg) { updateEnabledState(); } public void propertyChange(PropertyChangeEvent evt) { updateEnabledState(); } } class CopyAllRight extends AbstractAction implements Observer, PropertyChangeListener { public CopyAllRight() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "useallright.png"); putValue(Action.SMALL_ICON, icon); putValue(Action.SHORT_DESCRIPTION, tr("Copy all their elements to the target")); } public void actionPerformed(ActionEvent arg0) { model.copyAll(ListRole.THEIR_ENTRIES); model.setFrozen(true); } private void updateEnabledState() { setEnabled(model.getMergedEntries().isEmpty() && !model.isFrozen()); } public void update(Observable o, Object arg) { updateEnabledState(); } public void propertyChange(PropertyChangeEvent evt) { updateEnabledState(); } } class MoveUpMergedAction extends AbstractAction implements ListSelectionListener { public MoveUpMergedAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "moveup.png"); putValue(Action.SMALL_ICON, icon); if (icon == null) { putValue(Action.NAME, tr("Up")); } putValue(Action.SHORT_DESCRIPTION, tr("Move up the selected elements by one position.")); setEnabled(false); } public void actionPerformed(ActionEvent arg0) { int [] rows = mergedEntriesTable.getSelectedRows(); model.moveUpMerged(rows); } public void valueChanged(ListSelectionEvent e) { int [] rows = mergedEntriesTable.getSelectedRows(); setEnabled( rows != null && 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 { public MoveDownMergedAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "movedown.png"); 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); } public void actionPerformed(ActionEvent arg0) { int [] rows = mergedEntriesTable.getSelectedRows(); model.moveDownMerged(rows); } public void valueChanged(ListSelectionEvent e) { int [] rows = mergedEntriesTable.getSelectedRows(); setEnabled( rows != null && 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 { public RemoveMergedAction() { ImageIcon icon = ImageProvider.get("dialogs/conflict", "remove.png"); 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); } public void actionPerformed(ActionEvent arg0) { int [] rows = mergedEntriesTable.getSelectedRows(); model.removeMerged(rows); } public void valueChanged(ListSelectionEvent e) { int [] rows = mergedEntriesTable.getSelectedRows(); setEnabled( rows != null && rows.length > 0 ); } } static public interface FreezeActionProperties { String PROP_SELECTED = FreezeActionProperties.class.getName() + ".selected"; } /** * Action for freezing the current state of the list merger * */ class FreezeAction extends AbstractAction implements ItemListener, FreezeActionProperties { public FreezeAction() { putValue(Action.NAME, tr("Freeze")); putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements.")); putValue(PROP_SELECTED, false); setEnabled(true); } 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( new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(PROP_SELECTED)) { btn.setSelected((Boolean)evt.getNewValue()); } } } ); } 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 oldValue, 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)) ); } } public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(ListMergeModel.FROZEN_PROP)) { handlePropertyChangeFrozen((Boolean)evt.getOldValue(), (Boolean)evt.getNewValue()); } } public ListMergeModel<T> getModel() { return model; } public void update(Observable o, Object arg) { 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()) ); } /** * Synchronizes scrollbar adjustments between a set of * {@see Adjustable}s. Whenever the adjustment of one of * the registerd Adjustables is updated the adjustment of * the other registered Adjustables is adjusted too. * */ class AdjustmentSynchronizer implements AdjustmentListener { private final ArrayList<Adjustable> synchronizedAdjustables; private final HashMap<Adjustable, Boolean> enabledMap; private final Observable observable; public AdjustmentSynchronizer() { synchronizedAdjustables = new ArrayList<Adjustable>(); enabledMap = new HashMap<Adjustable, Boolean>(); observable = new Observable(); } /** * registers an {@see Adjustable} for participation in synchronized * scrolling. * * @param adjustable the adjustable */ public void participateInSynchronizedScrolling(Adjustable adjustable) { if (adjustable == null) return; if (synchronizedAdjustables.contains(adjustable)) return; synchronizedAdjustables.add(adjustable); setParticipatingInSynchronizedScrolling(adjustable, true); adjustable.addAdjustmentListener(this); } /** * event handler for {@see AdjustmentEvent}s * */ public void adjustmentValueChanged(AdjustmentEvent e) { if (! enabledMap.get(e.getAdjustable())) return; for (Adjustable a : synchronizedAdjustables) { if (a != e.getAdjustable() && isParticipatingInSynchronizedScrolling(a)) { a.setValue(e.getValue()); } } } /** * sets whether adjustable participates in adjustment synchronization * or not * * @param adjustable the adjustable */ protected void setParticipatingInSynchronizedScrolling(Adjustable adjustable, boolean isParticipating) { CheckParameterUtil.ensureParameterNotNull(adjustable, "adjustable"); if (! synchronizedAdjustables.contains(adjustable)) throw new IllegalStateException(tr("Adjustable {0} not registered yet. Cannot set participation in synchronized adjustment.", adjustable)); enabledMap.put(adjustable, isParticipating); observable.notifyObservers(); } /** * returns true if an adjustable is participating in synchronized scrolling * * @param adjustable the adjustable * @return true, if the adjustable is participating in synchronized scrolling, false otherwise * @throws IllegalStateException thrown, if adjustable is not registered for synchronized scrolling */ protected boolean isParticipatingInSynchronizedScrolling(Adjustable adjustable) throws IllegalStateException { if (! synchronizedAdjustables.contains(adjustable)) throw new IllegalStateException(tr("Adjustable {0} not registered yet.", adjustable)); return enabledMap.get(adjustable); } /** * wires a {@see JCheckBox} to the adjustment synchronizer, in such a way that: * <li> * <ol>state changes in the checkbox control whether the adjustable participates * in synchronized adjustment</ol> * <ol>state changes in this {@see AdjustmentSynchronizer} are reflected in the * {@see JCheckBox}</ol> * </li> * * * @param view the checkbox to control whether an adjustable participates in synchronized * adjustment * @param adjustable the adjustable * @exception IllegalArgumentException thrown, if view is null * @exception IllegalArgumentException thrown, if adjustable is null */ protected void adapt(final JCheckBox view, final Adjustable adjustable) throws IllegalStateException { CheckParameterUtil.ensureParameterNotNull(adjustable, "adjustable"); CheckParameterUtil.ensureParameterNotNull(view, "view"); if (! synchronizedAdjustables.contains(adjustable)) { participateInSynchronizedScrolling(adjustable); } // register an item lister with the check box // view.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { switch(e.getStateChange()) { case ItemEvent.SELECTED: if (!isParticipatingInSynchronizedScrolling(adjustable)) { setParticipatingInSynchronizedScrolling(adjustable, true); } break; case ItemEvent.DESELECTED: if (isParticipatingInSynchronizedScrolling(adjustable)) { setParticipatingInSynchronizedScrolling(adjustable, false); } break; } } }); observable.addObserver( new Observer() { public void update(Observable o, Object arg) { boolean sync = isParticipatingInSynchronizedScrolling(adjustable); if (view.isSelected() != sync) { view.setSelected(sync); } } } ); setParticipatingInSynchronizedScrolling(adjustable, true); view.setSelected(true); } } }