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