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 buildTable(PointInTimeType pointInTimeType) { 055 final DiffTableModel tableModel = model.getNodeListTableModel(pointInTimeType); 056 final NodeListTableColumnModel columnModel = new NodeListTableColumnModel(); 057 final JTable table = new JTable(tableModel, columnModel); 058 tableModel.addTableModelListener(new ReversedChangeListener(table, columnModel)); 059 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 060 selectionSynchronizer.participateInSynchronizedSelection(table.getSelectionModel()); 061 table.addMouseListener(new InternalPopupMenuLauncher()); 062 table.addMouseListener(new DoubleClickAdapter(table)); 063 return table; 064 } 065 066 static final class ReversedChangeListener implements TableModelListener { 067 private final NodeListTableColumnModel columnModel; 068 private final JTable table; 069 private Boolean reversed; 070 private final String nonReversedText; 071 private final String reversedText; 072 073 ReversedChangeListener(JTable table, NodeListTableColumnModel columnModel) { 074 this.columnModel = columnModel; 075 this.table = table; 076 nonReversedText = tr("Nodes") + (table.getFont().canDisplay('\u25bc') ? " \u25bc" : " (1-n)"); 077 reversedText = tr("Nodes") + (table.getFont().canDisplay('\u25b2') ? " \u25b2" : " (n-1)"); 078 } 079 080 @Override 081 public void tableChanged(TableModelEvent e) { 082 if (e.getSource() instanceof DiffTableModel) { 083 final DiffTableModel mod = (DiffTableModel) e.getSource(); 084 if (reversed == null || reversed != mod.isReversed()) { 085 reversed = mod.isReversed(); 086 columnModel.getColumn(0).setHeaderValue(reversed ? reversedText : nonReversedText); 087 table.getTableHeader().setToolTipText( 088 reversed ? tr("The nodes of this way are in reverse order") : null); 089 table.getTableHeader().repaint(); 090 } 091 } 092 } 093 } 094 095 static class NodeListPopupMenu extends JPopupMenu { 096 private final ZoomToNodeAction zoomToNodeAction; 097 private final ShowHistoryAction showHistoryAction; 098 099 NodeListPopupMenu() { 100 zoomToNodeAction = new ZoomToNodeAction(); 101 add(zoomToNodeAction); 102 showHistoryAction = new ShowHistoryAction(); 103 add(showHistoryAction); 104 } 105 106 public void prepare(PrimitiveId pid) { 107 zoomToNodeAction.setPrimitiveId(pid); 108 zoomToNodeAction.updateEnabledState(); 109 110 showHistoryAction.setPrimitiveId(pid); 111 showHistoryAction.updateEnabledState(); 112 } 113 } 114 115 static class ZoomToNodeAction extends AbstractAction { 116 private transient PrimitiveId primitiveId; 117 118 /** 119 * Constructs a new {@code ZoomToNodeAction}. 120 */ 121 ZoomToNodeAction() { 122 putValue(NAME, tr("Zoom to node")); 123 putValue(SHORT_DESCRIPTION, tr("Zoom to this node in the current data layer")); 124 new ImageProvider("dialogs", "zoomin").getResource().attachImageIcon(this, true); 125 } 126 127 @Override 128 public void actionPerformed(ActionEvent e) { 129 if (!isEnabled()) 130 return; 131 IPrimitive p = getPrimitiveToZoom(); 132 if (p != null) { 133 OsmData<?, ?, ?, ?> ds = MainApplication.getLayerManager().getActiveData(); 134 if (ds != null) { 135 ds.setSelected(p.getPrimitiveId()); 136 AutoScaleAction.autoScale(AutoScaleMode.SELECTION); 137 } 138 } 139 } 140 141 public void setPrimitiveId(PrimitiveId pid) { 142 this.primitiveId = pid; 143 updateEnabledState(); 144 } 145 146 protected IPrimitive getPrimitiveToZoom() { 147 if (primitiveId == null) 148 return null; 149 OsmData<?, ?, ?, ?> ds = MainApplication.getLayerManager().getActiveData(); 150 if (ds == null) 151 return null; 152 return ds.getPrimitiveById(primitiveId); 153 } 154 155 public void updateEnabledState() { 156 setEnabled(MainApplication.getLayerManager().getActiveData() != null && getPrimitiveToZoom() != null); 157 } 158 } 159 160 static class ShowHistoryAction extends AbstractAction { 161 private transient PrimitiveId primitiveId; 162 163 /** 164 * Constructs a new {@code ShowHistoryAction}. 165 */ 166 ShowHistoryAction() { 167 putValue(NAME, tr("Show history")); 168 putValue(SHORT_DESCRIPTION, tr("Open a history browser with the history of this node")); 169 new ImageProvider("dialogs", "history").getResource().attachImageIcon(this, true); 170 } 171 172 @Override 173 public void actionPerformed(ActionEvent e) { 174 if (isEnabled()) { 175 run(); 176 } 177 } 178 179 public void setPrimitiveId(PrimitiveId pid) { 180 this.primitiveId = pid; 181 updateEnabledState(); 182 } 183 184 public void run() { 185 if (HistoryDataSet.getInstance().getHistory(primitiveId) == null) { 186 MainApplication.worker.submit(new HistoryLoadTask().add(primitiveId)); 187 } 188 MainApplication.worker.submit(() -> { 189 final History h = HistoryDataSet.getInstance().getHistory(primitiveId); 190 if (h == null) 191 return; 192 GuiHelper.runInEDT(() -> HistoryBrowserDialogManager.getInstance().show(h)); 193 }); 194 } 195 196 public void updateEnabledState() { 197 setEnabled(primitiveId != null && !primitiveId.isNew()); 198 } 199 } 200 201 private static PrimitiveId primitiveIdAtRow(DiffTableModel model, int row) { 202 Long id = (Long) model.getValueAt(row, 0).value; 203 return id == null ? null : new SimplePrimitiveId(id, OsmPrimitiveType.NODE); 204 } 205 206 static class InternalPopupMenuLauncher extends PopupMenuLauncher { 207 InternalPopupMenuLauncher() { 208 super(new NodeListPopupMenu()); 209 } 210 211 @Override 212 protected int checkTableSelection(JTable table, Point p) { 213 int row = super.checkTableSelection(table, p); 214 ((NodeListPopupMenu) menu).prepare(primitiveIdAtRow((DiffTableModel) table.getModel(), row)); 215 return row; 216 } 217 } 218 219 static class DoubleClickAdapter extends MouseAdapter { 220 private final JTable table; 221 private final ShowHistoryAction showHistoryAction; 222 223 DoubleClickAdapter(JTable table) { 224 this.table = table; 225 showHistoryAction = new ShowHistoryAction(); 226 } 227 228 @Override 229 public void mouseClicked(MouseEvent e) { 230 if (e.getClickCount() < 2) 231 return; 232 int row = table.rowAtPoint(e.getPoint()); 233 if (row <= 0) 234 return; 235 PrimitiveId pid = primitiveIdAtRow((DiffTableModel) table.getModel(), row); 236 if (pid == null || pid.isNew()) 237 return; 238 showHistoryAction.setPrimitiveId(pid); 239 showHistoryAction.run(); 240 } 241 } 242}