001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.history; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Point; 007import java.awt.event.ActionEvent; 008import java.awt.event.MouseAdapter; 009import java.awt.event.MouseEvent; 010 011import javax.swing.AbstractAction; 012import javax.swing.JPopupMenu; 013import javax.swing.JTable; 014import javax.swing.ListSelectionModel; 015import javax.swing.event.TableModelEvent; 016import javax.swing.event.TableModelListener; 017 018import org.openstreetmap.josm.actions.AutoScaleAction; 019import org.openstreetmap.josm.actions.AutoScaleAction.AutoScaleMode; 020import org.openstreetmap.josm.data.osm.IPrimitive; 021import org.openstreetmap.josm.data.osm.OsmData; 022import org.openstreetmap.josm.data.osm.OsmPrimitive; 023import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 024import org.openstreetmap.josm.data.osm.PrimitiveId; 025import org.openstreetmap.josm.data.osm.SimplePrimitiveId; 026import org.openstreetmap.josm.data.osm.history.History; 027import org.openstreetmap.josm.data.osm.history.HistoryDataSet; 028import org.openstreetmap.josm.gui.MainApplication; 029import org.openstreetmap.josm.gui.util.GuiHelper; 030import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 031import org.openstreetmap.josm.tools.ImageProvider; 032 033/** 034 * NodeListViewer is a UI component which displays the node list of two 035 * version of a {@link OsmPrimitive} in a {@link History}. 036 * 037 * <ul> 038 * <li>on the left, it displays the node list for the version at {@link PointInTimeType#REFERENCE_POINT_IN_TIME}</li> 039 * <li>on the right, it displays the node list for the version at {@link PointInTimeType#CURRENT_POINT_IN_TIME}</li> 040 * </ul> 041 * @since 1709 042 */ 043public class NodeListViewer extends HistoryViewerPanel { 044 045 /** 046 * Constructs a new {@code NodeListViewer}. 047 * @param model history browser model 048 */ 049 public NodeListViewer(HistoryBrowserModel model) { 050 super(model); 051 } 052 053 @Override 054 protected JTable buildReferenceTable() { 055 return buildTable(PointInTimeType.REFERENCE_POINT_IN_TIME, "table.referencenodelisttable"); 056 } 057 058 @Override 059 protected JTable buildCurrentTable() { 060 return buildTable(PointInTimeType.CURRENT_POINT_IN_TIME, "table.currentnodelisttable"); 061 } 062 063 private JTable buildTable(PointInTimeType pointInTimeType, String name) { 064 final DiffTableModel tableModel = model.getNodeListTableModel(pointInTimeType); 065 final NodeListTableColumnModel columnModel = new NodeListTableColumnModel(); 066 final JTable table = new JTable(tableModel, columnModel); 067 tableModel.addTableModelListener(new ReversedChangeListener(table, columnModel)); 068 table.setName(name); 069 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 070 selectionSynchronizer.participateInSynchronizedSelection(table.getSelectionModel()); 071 table.addMouseListener(new InternalPopupMenuLauncher()); 072 table.addMouseListener(new DoubleClickAdapter(table)); 073 return table; 074 } 075 076 static final class ReversedChangeListener implements TableModelListener { 077 private final NodeListTableColumnModel columnModel; 078 private final JTable table; 079 private Boolean reversed; 080 private final String nonReversedText; 081 private final String reversedText; 082 083 ReversedChangeListener(JTable table, NodeListTableColumnModel columnModel) { 084 this.columnModel = columnModel; 085 this.table = table; 086 nonReversedText = tr("Nodes") + (table.getFont().canDisplay('\u25bc') ? " \u25bc" : " (1-n)"); 087 reversedText = tr("Nodes") + (table.getFont().canDisplay('\u25b2') ? " \u25b2" : " (n-1)"); 088 } 089 090 @Override 091 public void tableChanged(TableModelEvent e) { 092 if (e.getSource() instanceof DiffTableModel) { 093 final DiffTableModel mod = (DiffTableModel) e.getSource(); 094 if (reversed == null || reversed != mod.isReversed()) { 095 reversed = mod.isReversed(); 096 columnModel.getColumn(0).setHeaderValue(reversed ? reversedText : nonReversedText); 097 table.getTableHeader().setToolTipText( 098 reversed ? tr("The nodes of this way are in reverse order") : null); 099 table.getTableHeader().repaint(); 100 } 101 } 102 } 103 } 104 105 static class NodeListPopupMenu extends JPopupMenu { 106 private final ZoomToNodeAction zoomToNodeAction; 107 private final ShowHistoryAction showHistoryAction; 108 109 NodeListPopupMenu() { 110 zoomToNodeAction = new ZoomToNodeAction(); 111 add(zoomToNodeAction); 112 showHistoryAction = new ShowHistoryAction(); 113 add(showHistoryAction); 114 } 115 116 public void prepare(PrimitiveId pid) { 117 zoomToNodeAction.setPrimitiveId(pid); 118 zoomToNodeAction.updateEnabledState(); 119 120 showHistoryAction.setPrimitiveId(pid); 121 showHistoryAction.updateEnabledState(); 122 } 123 } 124 125 static class ZoomToNodeAction extends AbstractAction { 126 private transient PrimitiveId primitiveId; 127 128 /** 129 * Constructs a new {@code ZoomToNodeAction}. 130 */ 131 ZoomToNodeAction() { 132 putValue(NAME, tr("Zoom to node")); 133 putValue(SHORT_DESCRIPTION, tr("Zoom to this node in the current data layer")); 134 new ImageProvider("dialogs", "zoomin").getResource().attachImageIcon(this, true); 135 } 136 137 @Override 138 public void actionPerformed(ActionEvent e) { 139 if (!isEnabled()) 140 return; 141 IPrimitive p = getPrimitiveToZoom(); 142 if (p != null) { 143 OsmData<?, ?, ?, ?> ds = MainApplication.getLayerManager().getActiveData(); 144 if (ds != null) { 145 ds.setSelected(p.getPrimitiveId()); 146 AutoScaleAction.autoScale(AutoScaleMode.SELECTION); 147 } 148 } 149 } 150 151 public void setPrimitiveId(PrimitiveId pid) { 152 this.primitiveId = pid; 153 updateEnabledState(); 154 } 155 156 protected IPrimitive getPrimitiveToZoom() { 157 if (primitiveId == null) 158 return null; 159 OsmData<?, ?, ?, ?> ds = MainApplication.getLayerManager().getActiveData(); 160 if (ds == null) 161 return null; 162 return ds.getPrimitiveById(primitiveId); 163 } 164 165 public void updateEnabledState() { 166 setEnabled(MainApplication.getLayerManager().getActiveData() != null && getPrimitiveToZoom() != null); 167 } 168 } 169 170 static class ShowHistoryAction extends AbstractAction { 171 private transient PrimitiveId primitiveId; 172 173 /** 174 * Constructs a new {@code ShowHistoryAction}. 175 */ 176 ShowHistoryAction() { 177 putValue(NAME, tr("Show history")); 178 putValue(SHORT_DESCRIPTION, tr("Open a history browser with the history of this node")); 179 new ImageProvider("dialogs", "history").getResource().attachImageIcon(this, true); 180 } 181 182 @Override 183 public void actionPerformed(ActionEvent e) { 184 if (isEnabled()) { 185 run(); 186 } 187 } 188 189 public void setPrimitiveId(PrimitiveId pid) { 190 this.primitiveId = pid; 191 updateEnabledState(); 192 } 193 194 public void run() { 195 if (HistoryDataSet.getInstance().getHistory(primitiveId) == null) { 196 MainApplication.worker.submit(new HistoryLoadTask().add(primitiveId)); 197 } 198 MainApplication.worker.submit(() -> { 199 final History h = HistoryDataSet.getInstance().getHistory(primitiveId); 200 if (h == null) 201 return; 202 GuiHelper.runInEDT(() -> HistoryBrowserDialogManager.getInstance().show(h)); 203 }); 204 } 205 206 public void updateEnabledState() { 207 setEnabled(primitiveId != null && !primitiveId.isNew()); 208 } 209 } 210 211 private static PrimitiveId primitiveIdAtRow(DiffTableModel model, int row) { 212 Long id = (Long) model.getValueAt(row, 0).value; 213 return id == null ? null : new SimplePrimitiveId(id, OsmPrimitiveType.NODE); 214 } 215 216 static class InternalPopupMenuLauncher extends PopupMenuLauncher { 217 InternalPopupMenuLauncher() { 218 super(new NodeListPopupMenu()); 219 } 220 221 @Override 222 protected int checkTableSelection(JTable table, Point p) { 223 int row = super.checkTableSelection(table, p); 224 ((NodeListPopupMenu) menu).prepare(primitiveIdAtRow((DiffTableModel) table.getModel(), row)); 225 return row; 226 } 227 } 228 229 static class DoubleClickAdapter extends MouseAdapter { 230 private final JTable table; 231 private final ShowHistoryAction showHistoryAction; 232 233 DoubleClickAdapter(JTable table) { 234 this.table = table; 235 showHistoryAction = new ShowHistoryAction(); 236 } 237 238 @Override 239 public void mouseClicked(MouseEvent e) { 240 if (e.getClickCount() < 2) 241 return; 242 int row = table.rowAtPoint(e.getPoint()); 243 if (row <= 0) 244 return; 245 PrimitiveId pid = primitiveIdAtRow((DiffTableModel) table.getModel(), row); 246 if (pid == null || pid.isNew()) 247 return; 248 showHistoryAction.setPrimitiveId(pid); 249 showHistoryAction.run(); 250 } 251 } 252}