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    }