001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.history;
003
004import java.awt.GridBagConstraints;
005import java.awt.Insets;
006import java.awt.event.ActionEvent;
007import java.util.function.BiPredicate;
008import java.util.stream.IntStream;
009
010import javax.swing.AbstractAction;
011import javax.swing.JPopupMenu;
012import javax.swing.JScrollPane;
013import javax.swing.JTable;
014import javax.swing.ListSelectionModel;
015
016import org.openstreetmap.josm.actions.AutoScaleAction;
017import org.openstreetmap.josm.actions.AutoScaleAction.AutoScaleMode;
018import org.openstreetmap.josm.data.osm.IPrimitive;
019import org.openstreetmap.josm.data.osm.OsmData;
020import org.openstreetmap.josm.data.osm.PrimitiveId;
021import org.openstreetmap.josm.gui.MainApplication;
022import org.openstreetmap.josm.gui.util.AdjustmentSynchronizer;
023import org.openstreetmap.josm.tools.ImageProvider;
024
025/**
026 * Base class of {@link TagInfoViewer} and {@link RelationMemberListViewer}.
027 * @since 6207
028 */
029public abstract class HistoryViewerPanel extends HistoryBrowserPanel {
030
031    protected transient AdjustmentSynchronizer adjustmentSynchronizer;
032    protected transient SelectionSynchronizer selectionSynchronizer;
033
034    protected HistoryViewerPanel(HistoryBrowserModel model) {
035        setModel(model);
036        build();
037    }
038
039    private JScrollPane embedInScrollPane(JTable table) {
040        JScrollPane pane = new JScrollPane(table);
041        adjustmentSynchronizer.participateInSynchronizedScrolling(pane.getVerticalScrollBar());
042        return pane;
043    }
044
045    protected abstract JTable buildReferenceTable();
046
047    protected abstract JTable buildCurrentTable();
048
049    private void build() {
050        GridBagConstraints gc = new GridBagConstraints();
051
052        // ---------------------------
053        gc.gridx = 0;
054        gc.gridy = 0;
055        gc.gridwidth = 1;
056        gc.gridheight = 1;
057        gc.weightx = 0.5;
058        gc.weighty = 0.0;
059        gc.insets = new Insets(5, 5, 5, 0);
060        gc.fill = GridBagConstraints.HORIZONTAL;
061        gc.anchor = GridBagConstraints.FIRST_LINE_START;
062        referenceInfoPanel = new VersionInfoPanel(model, PointInTimeType.REFERENCE_POINT_IN_TIME);
063        add(referenceInfoPanel, gc);
064
065        gc.gridx = 1;
066        gc.gridy = 0;
067        gc.gridwidth = 1;
068        gc.gridheight = 1;
069        gc.fill = GridBagConstraints.HORIZONTAL;
070        gc.weightx = 0.5;
071        gc.weighty = 0.0;
072        gc.anchor = GridBagConstraints.FIRST_LINE_START;
073        currentInfoPanel = new VersionInfoPanel(model, PointInTimeType.CURRENT_POINT_IN_TIME);
074        add(currentInfoPanel, gc);
075
076        adjustmentSynchronizer = new AdjustmentSynchronizer();
077        selectionSynchronizer = new SelectionSynchronizer();
078
079        // ---------------------------
080        gc.gridx = 0;
081        gc.gridy = 1;
082        gc.gridwidth = 1;
083        gc.gridheight = 1;
084        gc.weightx = 0.5;
085        gc.weighty = 1.0;
086        gc.fill = GridBagConstraints.BOTH;
087        gc.anchor = GridBagConstraints.NORTHWEST;
088        add(embedInScrollPane(buildReferenceTable()), gc);
089
090        gc.gridx = 1;
091        gc.gridy = 1;
092        gc.gridwidth = 1;
093        gc.gridheight = 1;
094        gc.weightx = 0.5;
095        gc.weighty = 1.0;
096        gc.fill = GridBagConstraints.BOTH;
097        gc.anchor = GridBagConstraints.NORTHWEST;
098        add(embedInScrollPane(buildCurrentTable()), gc);
099    }
100
101    /**
102     * Enables semantic highlighting for the {@link org.openstreetmap.josm.data.StructUtils.SerializeOptions}
103     * @param thisSelectionModel selection model
104     * @param thisModel table model (corresponding to the selection model)
105     * @param otherModel table model for the other point in time
106     * @param isSemanticallyEquivalent predicate to determine whether the items should be highlighted
107     */
108    protected void enableSemanticSelectionSynchronization(ListSelectionModel thisSelectionModel,
109                                                          DiffTableModel thisModel, DiffTableModel otherModel,
110                                                          BiPredicate<TwoColumnDiff.Item, TwoColumnDiff.Item> isSemanticallyEquivalent) {
111        selectionSynchronizer.setSelectionIndexMapper((selection, sourceSelectionModel) -> {
112            DiffTableModel sourceModel = sourceSelectionModel == thisSelectionModel ? thisModel : otherModel;
113            DiffTableModel destinationModel = sourceSelectionModel == thisSelectionModel ? otherModel : thisModel;
114            return IntStream.range(0, destinationModel.getRowCount())
115                    .filter(i -> isSemanticallyEquivalent.test(sourceModel.getValueAt(selection, 0), destinationModel.getValueAt(i, 0)));
116        });
117    }
118
119    static class ListPopupMenu extends JPopupMenu {
120        private final ZoomToObjectAction zoomToObjectAction;
121        private final ShowHistoryAction showHistoryAction;
122
123        ListPopupMenu(String name, String shortDescription) {
124            zoomToObjectAction = new ZoomToObjectAction(name, shortDescription);
125            add(zoomToObjectAction);
126            showHistoryAction = new ShowHistoryAction();
127            add(showHistoryAction);
128        }
129
130        void prepare(PrimitiveId pid) {
131            zoomToObjectAction.setPrimitiveId(pid);
132            zoomToObjectAction.updateEnabledState();
133
134            showHistoryAction.setPrimitiveId(pid);
135            showHistoryAction.updateEnabledState();
136        }
137    }
138
139    static class ZoomToObjectAction extends AbstractAction {
140        private transient PrimitiveId primitiveId;
141
142        /**
143         * Constructs a new {@code ZoomToObjectAction}.
144         * @param name  name for the action
145         * @param shortDescription The key used for storing a short <code>String</code> description for the action, used for tooltip text.
146         */
147        ZoomToObjectAction(String name, String shortDescription) {
148            putValue(NAME, name);
149            putValue(SHORT_DESCRIPTION, shortDescription);
150            new ImageProvider("dialogs", "zoomin").getResource().attachImageIcon(this, true);
151        }
152
153        @Override
154        public void actionPerformed(ActionEvent e) {
155            if (!isEnabled())
156                return;
157            IPrimitive p = getPrimitiveToZoom();
158            if (p != null && p.isSelectable()) {
159                p.getDataSet().setSelected(p);
160                AutoScaleAction.autoScale(AutoScaleMode.SELECTION);
161            }
162        }
163
164        public void setPrimitiveId(PrimitiveId pid) {
165            this.primitiveId = pid;
166            updateEnabledState();
167        }
168
169        protected IPrimitive getPrimitiveToZoom() {
170            if (primitiveId == null)
171                return null;
172            OsmData<?, ?, ?, ?> ds = MainApplication.getLayerManager().getActiveData();
173            if (ds == null)
174                return null;
175            return ds.getPrimitiveById(primitiveId);
176        }
177
178        public void updateEnabledState() {
179            setEnabled(MainApplication.getLayerManager().getActiveData() != null && getPrimitiveToZoom() != null);
180        }
181    }
182}