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}