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.GridBagConstraints; 007import java.awt.GridBagLayout; 008import java.awt.Insets; 009import java.awt.Point; 010import java.awt.event.ActionEvent; 011import java.awt.event.MouseAdapter; 012import java.awt.event.MouseEvent; 013 014import javax.swing.AbstractAction; 015import javax.swing.JPanel; 016import javax.swing.JPopupMenu; 017import javax.swing.JScrollPane; 018import javax.swing.JTable; 019import javax.swing.ListSelectionModel; 020import javax.swing.table.TableModel; 021 022import org.openstreetmap.josm.Main; 023import org.openstreetmap.josm.actions.AutoScaleAction; 024import org.openstreetmap.josm.data.osm.OsmPrimitive; 025import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 026import org.openstreetmap.josm.data.osm.PrimitiveId; 027import org.openstreetmap.josm.data.osm.SimplePrimitiveId; 028import org.openstreetmap.josm.data.osm.history.History; 029import org.openstreetmap.josm.data.osm.history.HistoryDataSet; 030import org.openstreetmap.josm.gui.layer.OsmDataLayer; 031import org.openstreetmap.josm.gui.util.AdjustmentSynchronizer; 032import org.openstreetmap.josm.gui.util.GuiHelper; 033import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 034import org.openstreetmap.josm.tools.ImageProvider; 035 036/** 037 * NodeListViewer is a UI component which displays the node list of two 038 * version of a {@link OsmPrimitive} in a {@link History}. 039 * 040 * <ul> 041 * <li>on the left, it displays the node list for the version at {@link PointInTimeType#REFERENCE_POINT_IN_TIME}</li> 042 * <li>on the right, it displays the node list for the version at {@link PointInTimeType#CURRENT_POINT_IN_TIME}</li> 043 * </ul> 044 * 045 */ 046public class NodeListViewer extends JPanel { 047 048 private HistoryBrowserModel model; 049 private VersionInfoPanel referenceInfoPanel; 050 private VersionInfoPanel currentInfoPanel; 051 private AdjustmentSynchronizer adjustmentSynchronizer; 052 private SelectionSynchronizer selectionSynchronizer; 053 private NodeListPopupMenu popupMenu; 054 055 protected JScrollPane embeddInScrollPane(JTable table) { 056 JScrollPane pane = new JScrollPane(table); 057 adjustmentSynchronizer.participateInSynchronizedScrolling(pane.getVerticalScrollBar()); 058 return pane; 059 } 060 061 protected JTable buildReferenceNodeListTable() { 062 JTable table = new JTable( 063 model.getNodeListTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME), 064 new NodeListTableColumnModel() 065 ); 066 table.setName("table.referencenodelisttable"); 067 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 068 selectionSynchronizer.participateInSynchronizedSelection(table.getSelectionModel()); 069 table.addMouseListener(new InternalPopupMenuLauncher()); 070 table.addMouseListener(new DoubleClickAdapter(table)); 071 return table; 072 } 073 074 protected JTable buildCurrentNodeListTable() { 075 JTable table = new JTable( 076 model.getNodeListTableModel(PointInTimeType.CURRENT_POINT_IN_TIME), 077 new NodeListTableColumnModel() 078 ); 079 table.setName("table.currentnodelisttable"); 080 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 081 selectionSynchronizer.participateInSynchronizedSelection(table.getSelectionModel()); 082 table.addMouseListener(new InternalPopupMenuLauncher()); 083 table.addMouseListener(new DoubleClickAdapter(table)); 084 return table; 085 } 086 087 protected void build() { 088 setLayout(new GridBagLayout()); 089 GridBagConstraints gc = new GridBagConstraints(); 090 091 // --------------------------- 092 gc.gridx = 0; 093 gc.gridy = 0; 094 gc.gridwidth = 1; 095 gc.gridheight = 1; 096 gc.weightx = 0.5; 097 gc.weighty = 0.0; 098 gc.insets = new Insets(5,5,5,0); 099 gc.fill = GridBagConstraints.HORIZONTAL; 100 gc.anchor = GridBagConstraints.FIRST_LINE_START; 101 referenceInfoPanel = new VersionInfoPanel(model, PointInTimeType.REFERENCE_POINT_IN_TIME); 102 add(referenceInfoPanel,gc); 103 104 gc.gridx = 1; 105 gc.gridy = 0; 106 gc.gridwidth = 1; 107 gc.gridheight = 1; 108 gc.fill = GridBagConstraints.HORIZONTAL; 109 gc.weightx = 0.5; 110 gc.weighty = 0.0; 111 gc.anchor = GridBagConstraints.FIRST_LINE_START; 112 currentInfoPanel = new VersionInfoPanel(model, PointInTimeType.CURRENT_POINT_IN_TIME); 113 add(currentInfoPanel,gc); 114 115 adjustmentSynchronizer = new AdjustmentSynchronizer(); 116 selectionSynchronizer = new SelectionSynchronizer(); 117 118 popupMenu = new NodeListPopupMenu(); 119 120 // --------------------------- 121 gc.gridx = 0; 122 gc.gridy = 1; 123 gc.gridwidth = 1; 124 gc.gridheight = 1; 125 gc.weightx = 0.5; 126 gc.weighty = 1.0; 127 gc.fill = GridBagConstraints.BOTH; 128 gc.anchor = GridBagConstraints.NORTHWEST; 129 add(embeddInScrollPane(buildReferenceNodeListTable()),gc); 130 131 gc.gridx = 1; 132 gc.gridy = 1; 133 gc.gridwidth = 1; 134 gc.gridheight = 1; 135 gc.weightx = 0.5; 136 gc.weighty = 1.0; 137 gc.fill = GridBagConstraints.BOTH; 138 gc.anchor = GridBagConstraints.NORTHWEST; 139 add(embeddInScrollPane(buildCurrentNodeListTable()),gc); 140 } 141 142 public NodeListViewer(HistoryBrowserModel model) { 143 setModel(model); 144 build(); 145 } 146 147 protected void unregisterAsObserver(HistoryBrowserModel model) { 148 if (currentInfoPanel != null) { 149 model.deleteObserver(currentInfoPanel); 150 } 151 if (referenceInfoPanel != null) { 152 model.deleteObserver(referenceInfoPanel); 153 } 154 } 155 protected void registerAsObserver(HistoryBrowserModel model) { 156 if (currentInfoPanel != null) { 157 model.addObserver(currentInfoPanel); 158 } 159 if (referenceInfoPanel != null) { 160 model.addObserver(referenceInfoPanel); 161 } 162 } 163 164 public void setModel(HistoryBrowserModel model) { 165 if (this.model != null) { 166 unregisterAsObserver(model); 167 } 168 this.model = model; 169 if (this.model != null) { 170 registerAsObserver(model); 171 } 172 } 173 174 static class NodeListPopupMenu extends JPopupMenu { 175 private final ZoomToNodeAction zoomToNodeAction; 176 private final ShowHistoryAction showHistoryAction; 177 178 public NodeListPopupMenu() { 179 zoomToNodeAction = new ZoomToNodeAction(); 180 add(zoomToNodeAction); 181 showHistoryAction = new ShowHistoryAction(); 182 add(showHistoryAction); 183 } 184 185 public void prepare(PrimitiveId pid){ 186 zoomToNodeAction.setPrimitiveId(pid); 187 zoomToNodeAction.updateEnabledState(); 188 189 showHistoryAction.setPrimitiveId(pid); 190 showHistoryAction.updateEnabledState(); 191 } 192 } 193 194 static class ZoomToNodeAction extends AbstractAction { 195 private PrimitiveId primitiveId; 196 197 public ZoomToNodeAction() { 198 putValue(NAME, tr("Zoom to node")); 199 putValue(SHORT_DESCRIPTION, tr("Zoom to this node in the current data layer")); 200 putValue(SMALL_ICON, ImageProvider.get("dialogs", "zoomin")); 201 } 202 203 @Override 204 public void actionPerformed(ActionEvent e) { 205 if (!isEnabled()) return; 206 OsmPrimitive p = getPrimitiveToZoom(); 207 if (p != null) { 208 OsmDataLayer editLayer = Main.main.getEditLayer(); 209 if (editLayer != null) { 210 editLayer.data.setSelected(p.getPrimitiveId()); 211 AutoScaleAction.autoScale("selection"); 212 } 213 } 214 } 215 216 public void setPrimitiveId(PrimitiveId pid) { 217 this.primitiveId = pid; 218 updateEnabledState(); 219 } 220 221 protected OsmPrimitive getPrimitiveToZoom() { 222 if (primitiveId == null) return null; 223 OsmDataLayer editLayer = Main.main.getEditLayer(); 224 if (editLayer == null) return null; 225 return editLayer.data.getPrimitiveById(primitiveId); 226 } 227 228 public void updateEnabledState() { 229 if (!Main.main.hasEditLayer()) { 230 setEnabled(false); 231 return; 232 } 233 setEnabled(getPrimitiveToZoom() != null); 234 } 235 } 236 237 static class ShowHistoryAction extends AbstractAction { 238 private PrimitiveId primitiveId; 239 240 public ShowHistoryAction() { 241 putValue(NAME, tr("Show history")); 242 putValue(SHORT_DESCRIPTION, tr("Open a history browser with the history of this node")); 243 putValue(SMALL_ICON, ImageProvider.get("dialogs", "history")); 244 } 245 246 @Override 247 public void actionPerformed(ActionEvent e) { 248 if (!isEnabled()) return; 249 run(); 250 } 251 252 public void setPrimitiveId(PrimitiveId pid) { 253 this.primitiveId = pid; 254 updateEnabledState(); 255 } 256 257 public void run() { 258 if (HistoryDataSet.getInstance().getHistory(primitiveId) == null) { 259 Main.worker.submit(new HistoryLoadTask().add(primitiveId)); 260 } 261 Runnable r = new Runnable() { 262 @Override 263 public void run() { 264 final History h = HistoryDataSet.getInstance().getHistory(primitiveId); 265 if (h == null) 266 return; 267 GuiHelper.runInEDT(new Runnable() { 268 @Override public void run() { 269 HistoryBrowserDialogManager.getInstance().show(h); 270 } 271 }); 272 } 273 }; 274 Main.worker.submit(r); 275 } 276 277 public void updateEnabledState() { 278 setEnabled(primitiveId != null && !primitiveId.isNew()); 279 } 280 } 281 282 private static PrimitiveId primitiveIdAtRow(TableModel model, int row) { 283 DiffTableModel castedModel = (DiffTableModel) model; 284 Long id = (Long)castedModel.getValueAt(row, 0).value; 285 if(id == null) return null; 286 return new SimplePrimitiveId(id, OsmPrimitiveType.NODE); 287 } 288 289 class InternalPopupMenuLauncher extends PopupMenuLauncher { 290 public InternalPopupMenuLauncher() { 291 super(popupMenu); 292 } 293 294 @Override protected int checkTableSelection(JTable table, Point p) { 295 int row = super.checkTableSelection(table, p); 296 popupMenu.prepare(primitiveIdAtRow(table.getModel(), row)); 297 return row; 298 } 299 } 300 301 static class DoubleClickAdapter extends MouseAdapter { 302 private JTable table; 303 private ShowHistoryAction showHistoryAction; 304 305 public DoubleClickAdapter(JTable table) { 306 this.table = table; 307 showHistoryAction = new ShowHistoryAction(); 308 } 309 310 @Override 311 public void mouseClicked(MouseEvent e) { 312 if (e.getClickCount() < 2) return; 313 int row = table.rowAtPoint(e.getPoint()); 314 if(row <= 0) return; 315 PrimitiveId pid = primitiveIdAtRow(table.getModel(), row); 316 if (pid == null || pid.isNew()) 317 return; 318 showHistoryAction.setPrimitiveId(pid); 319 showHistoryAction.run(); 320 } 321 } 322}