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