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}