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 }