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}