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.BorderLayout;
007import java.awt.Dimension;
008import java.util.Arrays;
009
010import javax.swing.JPanel;
011import javax.swing.JScrollPane;
012import javax.swing.JSplitPane;
013import javax.swing.JTabbedPane;
014
015import org.openstreetmap.josm.data.osm.OsmPrimitive;
016import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
017import org.openstreetmap.josm.data.osm.history.History;
018import org.openstreetmap.josm.tools.Destroyable;
019
020/**
021 * HistoryBrowser is an UI component which displays history information about an {@link OsmPrimitive}.
022 *
023 * @since 1709
024 */
025public class HistoryBrowser extends JPanel implements Destroyable {
026
027    /** the model */
028    private transient HistoryBrowserModel model;
029    private TagInfoViewer tagInfoViewer;
030    private NodeListViewer nodeListViewer;
031    private RelationMemberListViewer relationMemberListViewer;
032    private CoordinateInfoViewer coordinateInfoViewer;
033    private JTabbedPane tpViewers;
034
035    /**
036     * Constructs a new {@code HistoryBrowser}.
037     */
038    public HistoryBrowser() {
039        model = new HistoryBrowserModel();
040        build();
041    }
042
043    /**
044     * Constructs a new {@code HistoryBrowser}.
045     * @param history the history of an {@link OsmPrimitive}
046     */
047    public HistoryBrowser(History history) {
048        this();
049        populate(history);
050    }
051
052    /**
053     * creates the table which shows the list of versions
054     *
055     * @return  the panel with the version table
056     */
057    protected JPanel createVersionTablePanel() {
058        JPanel pnl = new JPanel(new BorderLayout());
059        pnl.add(new JScrollPane(new VersionTable(model)), BorderLayout.CENTER);
060        return pnl;
061    }
062
063    /**
064     * creates the panel which shows information about two different versions
065     * of the same {@link OsmPrimitive}.
066     *
067     * @return the panel
068     */
069    protected JPanel createVersionComparePanel() {
070        tpViewers = new JTabbedPane();
071
072        // create the viewers, but don't add them yet.
073        // see populate()
074        //
075        tagInfoViewer = new TagInfoViewer(model);
076        nodeListViewer = new NodeListViewer(model);
077        relationMemberListViewer = new RelationMemberListViewer(model);
078        coordinateInfoViewer = new CoordinateInfoViewer(model);
079        JPanel pnl = new JPanel(new BorderLayout());
080        pnl.add(tpViewers, BorderLayout.CENTER);
081
082        tpViewers.addChangeListener(e -> {
083            if (tpViewers.getSelectedComponent() == coordinateInfoViewer) {
084                // while building the component size is not yet known, thus panning does not give reasonable results
085                coordinateInfoViewer.setDisplayToFitMapMarkers();
086            }
087        });
088
089        return pnl;
090    }
091
092    /**
093     * builds the GUI
094     */
095    protected void build() {
096        JPanel left = createVersionTablePanel();
097        JPanel right = createVersionComparePanel();
098        setLayout(new BorderLayout());
099        JSplitPane pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right);
100        add(pane, BorderLayout.CENTER);
101
102        pane.setOneTouchExpandable(true);
103        pane.setDividerLocation(300);
104
105        Dimension minimumSize = new Dimension(100, 50);
106        left.setMinimumSize(minimumSize);
107        right.setMinimumSize(minimumSize);
108    }
109
110    /**
111     * populates the browser with the history of a specific {@link OsmPrimitive}
112     *
113     * @param history the history
114     */
115    public void populate(History history) {
116        boolean samePrimitive = model.isSamePrimitive(history); // needs to be before setHistory
117        model.setHistory(history);
118        if (samePrimitive) {
119            // no need to rebuild the UI
120            return;
121        }
122
123        tpViewers.removeAll();
124
125        tpViewers.add(tagInfoViewer);
126        tpViewers.setTitleAt(0, tr("Tags"));
127
128        if (history.getEarliest().getType() == OsmPrimitiveType.NODE) {
129            tpViewers.add(coordinateInfoViewer);
130            tpViewers.setTitleAt(1, tr("Coordinates"));
131        } else if (history.getEarliest().getType() == OsmPrimitiveType.WAY) {
132            tpViewers.add(nodeListViewer);
133            tpViewers.setTitleAt(1, tr("Nodes"));
134        } else if (history.getEarliest().getType() == OsmPrimitiveType.RELATION) {
135            tpViewers.add(relationMemberListViewer);
136            tpViewers.setTitleAt(1, tr("Members"));
137        }
138        revalidate();
139    }
140
141    /**
142     * replies the {@link History} currently displayed by this browser
143     *
144     * @return the current history
145     */
146    public History getHistory() {
147        return model.getHistory();
148    }
149
150    /**
151     * replies the model used by this browser
152     * @return the model
153     */
154    public HistoryBrowserModel getModel() {
155        return model;
156    }
157
158    @Override
159    public void destroy() {
160        if (model != null) {
161            model.unlinkAsListener();
162            model = null;
163        }
164        Arrays.asList(tagInfoViewer, nodeListViewer, relationMemberListViewer, coordinateInfoViewer).stream()
165                .filter(Destroyable.class::isInstance).forEach(Destroyable::destroy);
166        tagInfoViewer = null;
167        nodeListViewer = null;
168        relationMemberListViewer = null;
169        coordinateInfoViewer = null;
170    }
171}