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.Dimension;
007import java.awt.Point;
008import java.util.ArrayList;
009import java.util.Collection;
010import java.util.HashMap;
011import java.util.List;
012import java.util.Map;
013
014import javax.swing.JOptionPane;
015import javax.swing.SwingUtilities;
016
017import org.openstreetmap.josm.Main;
018import org.openstreetmap.josm.data.osm.PrimitiveId;
019import org.openstreetmap.josm.data.osm.history.History;
020import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
021import org.openstreetmap.josm.gui.MapView;
022import org.openstreetmap.josm.gui.layer.Layer;
023import org.openstreetmap.josm.tools.BugReportExceptionHandler;
024import org.openstreetmap.josm.tools.Predicate;
025import org.openstreetmap.josm.tools.Utils;
026import org.openstreetmap.josm.tools.WindowGeometry;
027
028public class HistoryBrowserDialogManager implements MapView.LayerChangeListener {
029    private static HistoryBrowserDialogManager instance;
030    public static HistoryBrowserDialogManager getInstance() {
031        if (instance == null) {
032            instance = new HistoryBrowserDialogManager();
033        }
034        return instance;
035    }
036
037    private Map<Long, HistoryBrowserDialog> dialogs;
038
039    protected HistoryBrowserDialogManager() {
040        dialogs = new HashMap<>();
041        MapView.addLayerChangeListener(this);
042    }
043
044    public boolean existsDialog(long id) {
045        return dialogs.containsKey(id);
046    }
047
048    public void show(long id, HistoryBrowserDialog dialog) {
049        if (dialogs.values().contains(dialog)) {
050            show(id);
051        } else {
052            placeOnScreen(dialog);
053            dialog.setVisible(true);
054            dialogs.put(id, dialog);
055        }
056    }
057
058    public void show(long id) {
059        if (dialogs.keySet().contains(id)) {
060            dialogs.get(id).toFront();
061        }
062    }
063
064    protected boolean hasDialogWithCloseUpperLeftCorner(Point p) {
065        for (HistoryBrowserDialog dialog: dialogs.values()) {
066            Point corner = dialog.getLocation();
067            if (p.x >= corner.x -5 && corner.x + 5 >= p.x
068                    && p.y >= corner.y -5 && corner.y + 5 >= p.y)
069                return true;
070        }
071        return false;
072    }
073
074    final String WINDOW_GEOMETRY_PREF = getClass().getName() + ".geometry";
075
076    public void placeOnScreen(HistoryBrowserDialog dialog) {
077        WindowGeometry geometry = new WindowGeometry(WINDOW_GEOMETRY_PREF, WindowGeometry.centerOnScreen(new Dimension(850, 500)));
078        geometry.applySafe(dialog);
079        Point p = dialog.getLocation();
080        while(hasDialogWithCloseUpperLeftCorner(p)) {
081            p.x += 20;
082            p.y += 20;
083        }
084        dialog.setLocation(p);
085    }
086
087    public void hide(HistoryBrowserDialog dialog) {
088        long id = 0;
089        for (long i: dialogs.keySet()) {
090            if (dialogs.get(i) == dialog) {
091                id = i;
092                break;
093            }
094        }
095        if (id > 0) {
096            dialogs.remove(id);
097            if (dialogs.isEmpty()) {
098                new WindowGeometry(dialog).remember(WINDOW_GEOMETRY_PREF);
099            }
100        }
101        dialog.setVisible(false);
102        dialog.dispose();
103    }
104
105    /**
106     * Hides and destroys all currently visible history browser dialogs
107     *
108     */
109    public void hideAll() {
110        List<HistoryBrowserDialog> dialogs = new ArrayList<>();
111        dialogs.addAll(this.dialogs.values());
112        for (HistoryBrowserDialog dialog: dialogs) {
113            dialog.unlinkAsListener();
114            hide(dialog);
115        }
116    }
117
118    public void show(History h) {
119        if (h == null)
120            return;
121        if (existsDialog(h.getId())) {
122            show(h.getId());
123        } else {
124            HistoryBrowserDialog dialog = new HistoryBrowserDialog(h);
125            show(h.getId(), dialog);
126        }
127    }
128
129    /* ----------------------------------------------------------------------------- */
130    /* LayerChangeListener                                                           */
131    /* ----------------------------------------------------------------------------- */
132    @Override
133    public void activeLayerChange(Layer oldLayer, Layer newLayer) {}
134    @Override
135    public void layerAdded(Layer newLayer) {}
136
137    @Override
138    public void layerRemoved(Layer oldLayer) {
139        // remove all history browsers if the number of layers drops to 0
140        //
141        if (Main.isDisplayingMapView() && Main.map.mapView.getNumLayers() == 0) {
142            hideAll();
143        }
144    }
145
146    public void showHistory(final Collection<? extends PrimitiveId> primitives) {
147        final Collection<? extends PrimitiveId> notNewPrimitives = Utils.filter(primitives, notNewPredicate);
148        if (notNewPrimitives.isEmpty()) {
149            JOptionPane.showMessageDialog(
150                    Main.parent,
151                    tr("Please select at least one already uploaded node, way, or relation."),
152                    tr("Warning"),
153                    JOptionPane.WARNING_MESSAGE);
154            return;
155        }
156
157        Collection<PrimitiveId> toLoad = Utils.filter(primitives, unloadedHistoryPredicate);
158        if (!toLoad.isEmpty()) {
159            HistoryLoadTask task = new HistoryLoadTask();
160            for (PrimitiveId p : notNewPrimitives) {
161                task.add(p);
162            }
163            Main.worker.submit(task);
164        }
165
166        Runnable r = new Runnable() {
167
168            @Override
169            public void run() {
170                try {
171                    for (PrimitiveId p : notNewPrimitives) {
172                        final History h = HistoryDataSet.getInstance().getHistory(p);
173                        if (h == null) {
174                            continue;
175                        }
176                        SwingUtilities.invokeLater(new Runnable() {
177                            @Override
178                            public void run() {
179                                show(h);
180                            }
181                        });
182                    }
183                } catch (final Exception e) {
184                    BugReportExceptionHandler.handleException(e);
185                }
186            }
187        };
188        Main.worker.submit(r);
189    }
190
191    private final Predicate<PrimitiveId> unloadedHistoryPredicate = new Predicate<PrimitiveId>() {
192
193        HistoryDataSet hds = HistoryDataSet.getInstance();
194
195        @Override
196        public boolean evaluate(PrimitiveId p) {
197            History h = hds.getHistory(p);
198            if (h == null)
199                // reload if the history is not in the cache yet
200                return true;
201            else
202                // reload if the history object of the selected object is not in the cache yet
203                return (!p.isNew() && h.getByVersion(p.getUniqueId()) == null);
204        }
205    };
206
207    private final Predicate<PrimitiveId> notNewPredicate = new Predicate<PrimitiveId>() {
208
209        @Override
210        public boolean evaluate(PrimitiveId p) {
211            return !p.isNew();
212        }
213    };
214}