[2512] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.gui.history;
|
---|
| 3 |
|
---|
[4408] | 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
[2512] | 6 | import java.awt.Dimension;
|
---|
| 7 | import java.awt.Point;
|
---|
| 8 | import java.util.ArrayList;
|
---|
[4408] | 9 | import java.util.Collection;
|
---|
[2512] | 10 | import java.util.HashMap;
|
---|
[8332] | 11 | import java.util.Iterator;
|
---|
[6316] | 12 | import java.util.List;
|
---|
[2512] | 13 | import java.util.Map;
|
---|
[8332] | 14 | import java.util.Map.Entry;
|
---|
| 15 | import java.util.Objects;
|
---|
[4760] | 16 |
|
---|
[4408] | 17 | import javax.swing.JOptionPane;
|
---|
| 18 | import javax.swing.SwingUtilities;
|
---|
[2512] | 19 |
|
---|
| 20 | import org.openstreetmap.josm.Main;
|
---|
[6448] | 21 | import org.openstreetmap.josm.data.osm.PrimitiveId;
|
---|
[2512] | 22 | import org.openstreetmap.josm.data.osm.history.History;
|
---|
[4408] | 23 | import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
|
---|
[10345] | 24 | import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
|
---|
| 25 | import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
|
---|
| 26 | import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
|
---|
| 27 | import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
|
---|
[4408] | 28 | import org.openstreetmap.josm.tools.Predicate;
|
---|
| 29 | import org.openstreetmap.josm.tools.Utils;
|
---|
[2512] | 30 | import org.openstreetmap.josm.tools.WindowGeometry;
|
---|
[10055] | 31 | import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
|
---|
[2512] | 32 |
|
---|
[7527] | 33 | /**
|
---|
| 34 | * Manager allowing to show/hide history dialogs.
|
---|
| 35 | * @since 2019
|
---|
| 36 | */
|
---|
[10345] | 37 | public final class HistoryBrowserDialogManager implements LayerChangeListener {
|
---|
[7527] | 38 |
|
---|
[8346] | 39 | private static final String WINDOW_GEOMETRY_PREF = HistoryBrowserDialogManager.class.getName() + ".geometry";
|
---|
| 40 |
|
---|
[6883] | 41 | private static HistoryBrowserDialogManager instance;
|
---|
[7527] | 42 |
|
---|
| 43 | /**
|
---|
| 44 | * Replies the unique instance.
|
---|
| 45 | * @return the unique instance
|
---|
| 46 | */
|
---|
[8126] | 47 | public static synchronized HistoryBrowserDialogManager getInstance() {
|
---|
[2512] | 48 | if (instance == null) {
|
---|
| 49 | instance = new HistoryBrowserDialogManager();
|
---|
| 50 | }
|
---|
| 51 | return instance;
|
---|
| 52 | }
|
---|
| 53 |
|
---|
[9078] | 54 | private final Map<Long, HistoryBrowserDialog> dialogs;
|
---|
[2512] | 55 |
|
---|
| 56 | protected HistoryBrowserDialogManager() {
|
---|
[7005] | 57 | dialogs = new HashMap<>();
|
---|
[10345] | 58 | Main.getLayerManager().addLayerChangeListener(this);
|
---|
[2512] | 59 | }
|
---|
| 60 |
|
---|
[7527] | 61 | /**
|
---|
| 62 | * Determines if an history dialog exists for the given object id.
|
---|
| 63 | * @param id the object id
|
---|
| 64 | * @return {@code true} if an history dialog exists for the given object id, {@code false} otherwise
|
---|
| 65 | */
|
---|
[2512] | 66 | public boolean existsDialog(long id) {
|
---|
| 67 | return dialogs.containsKey(id);
|
---|
| 68 | }
|
---|
| 69 |
|
---|
[7527] | 70 | protected void show(long id, HistoryBrowserDialog dialog) {
|
---|
[2512] | 71 | if (dialogs.values().contains(dialog)) {
|
---|
| 72 | show(id);
|
---|
| 73 | } else {
|
---|
| 74 | placeOnScreen(dialog);
|
---|
| 75 | dialog.setVisible(true);
|
---|
| 76 | dialogs.put(id, dialog);
|
---|
| 77 | }
|
---|
| 78 | }
|
---|
| 79 |
|
---|
[7527] | 80 | protected void show(long id) {
|
---|
[2512] | 81 | if (dialogs.keySet().contains(id)) {
|
---|
| 82 | dialogs.get(id).toFront();
|
---|
| 83 | }
|
---|
| 84 | }
|
---|
| 85 |
|
---|
| 86 | protected boolean hasDialogWithCloseUpperLeftCorner(Point p) {
|
---|
| 87 | for (HistoryBrowserDialog dialog: dialogs.values()) {
|
---|
| 88 | Point corner = dialog.getLocation();
|
---|
| 89 | if (p.x >= corner.x -5 && corner.x + 5 >= p.x
|
---|
| 90 | && p.y >= corner.y -5 && corner.y + 5 >= p.y)
|
---|
| 91 | return true;
|
---|
| 92 | }
|
---|
| 93 | return false;
|
---|
| 94 | }
|
---|
| 95 |
|
---|
[7527] | 96 | protected void placeOnScreen(HistoryBrowserDialog dialog) {
|
---|
[6743] | 97 | WindowGeometry geometry = new WindowGeometry(WINDOW_GEOMETRY_PREF, WindowGeometry.centerOnScreen(new Dimension(850, 500)));
|
---|
[4932] | 98 | geometry.applySafe(dialog);
|
---|
[2512] | 99 | Point p = dialog.getLocation();
|
---|
[7527] | 100 | while (hasDialogWithCloseUpperLeftCorner(p)) {
|
---|
[6743] | 101 | p.x += 20;
|
---|
[2512] | 102 | p.y += 20;
|
---|
| 103 | }
|
---|
| 104 | dialog.setLocation(p);
|
---|
| 105 | }
|
---|
| 106 |
|
---|
[7527] | 107 | /**
|
---|
| 108 | * Hides the specified history dialog and cleans associated resources.
|
---|
| 109 | * @param dialog History dialog to hide
|
---|
| 110 | */
|
---|
[2512] | 111 | public void hide(HistoryBrowserDialog dialog) {
|
---|
[8465] | 112 | for (Iterator<Entry<Long, HistoryBrowserDialog>> it = dialogs.entrySet().iterator(); it.hasNext();) {
|
---|
[8332] | 113 | if (Objects.equals(it.next().getValue(), dialog)) {
|
---|
| 114 | it.remove();
|
---|
| 115 | if (dialogs.isEmpty()) {
|
---|
| 116 | new WindowGeometry(dialog).remember(WINDOW_GEOMETRY_PREF);
|
---|
| 117 | }
|
---|
[2512] | 118 | break;
|
---|
| 119 | }
|
---|
| 120 | }
|
---|
| 121 | dialog.setVisible(false);
|
---|
| 122 | dialog.dispose();
|
---|
| 123 | }
|
---|
| 124 |
|
---|
| 125 | /**
|
---|
| 126 | * Hides and destroys all currently visible history browser dialogs
|
---|
| 127 | *
|
---|
| 128 | */
|
---|
| 129 | public void hideAll() {
|
---|
[7005] | 130 | List<HistoryBrowserDialog> dialogs = new ArrayList<>();
|
---|
[2512] | 131 | dialogs.addAll(this.dialogs.values());
|
---|
| 132 | for (HistoryBrowserDialog dialog: dialogs) {
|
---|
| 133 | dialog.unlinkAsListener();
|
---|
| 134 | hide(dialog);
|
---|
| 135 | }
|
---|
| 136 | }
|
---|
| 137 |
|
---|
[7527] | 138 | /**
|
---|
| 139 | * Show history dialog for the given history.
|
---|
| 140 | * @param h History to show
|
---|
| 141 | */
|
---|
[2512] | 142 | public void show(History h) {
|
---|
| 143 | if (h == null)
|
---|
| 144 | return;
|
---|
| 145 | if (existsDialog(h.getId())) {
|
---|
| 146 | show(h.getId());
|
---|
| 147 | } else {
|
---|
| 148 | HistoryBrowserDialog dialog = new HistoryBrowserDialog(h);
|
---|
| 149 | show(h.getId(), dialog);
|
---|
| 150 | }
|
---|
| 151 | }
|
---|
| 152 |
|
---|
| 153 | /* ----------------------------------------------------------------------------- */
|
---|
| 154 | /* LayerChangeListener */
|
---|
| 155 | /* ----------------------------------------------------------------------------- */
|
---|
[6084] | 156 | @Override
|
---|
[10345] | 157 | public void layerAdded(LayerAddEvent e) {
|
---|
[10173] | 158 | // Do nothing
|
---|
| 159 | }
|
---|
[8510] | 160 |
|
---|
[6084] | 161 | @Override
|
---|
[10345] | 162 | public void layerRemoving(LayerRemoveEvent e) {
|
---|
[2512] | 163 | // remove all history browsers if the number of layers drops to 0
|
---|
[10345] | 164 | if (e.getSource().getLayers().isEmpty()) {
|
---|
[2512] | 165 | hideAll();
|
---|
| 166 | }
|
---|
| 167 | }
|
---|
[4408] | 168 |
|
---|
[10345] | 169 | @Override
|
---|
| 170 | public void layerOrderChanged(LayerOrderChangeEvent e) {
|
---|
| 171 | // Do nothing
|
---|
| 172 | }
|
---|
| 173 |
|
---|
[7527] | 174 | /**
|
---|
| 175 | * Show history dialog(s) for the given primitive(s).
|
---|
| 176 | * @param primitives The primitive(s) for which history will be displayed
|
---|
| 177 | */
|
---|
[6448] | 178 | public void showHistory(final Collection<? extends PrimitiveId> primitives) {
|
---|
| 179 | final Collection<? extends PrimitiveId> notNewPrimitives = Utils.filter(primitives, notNewPredicate);
|
---|
[4760] | 180 | if (notNewPrimitives.isEmpty()) {
|
---|
| 181 | JOptionPane.showMessageDialog(
|
---|
| 182 | Main.parent,
|
---|
| 183 | tr("Please select at least one already uploaded node, way, or relation."),
|
---|
| 184 | tr("Warning"),
|
---|
| 185 | JOptionPane.WARNING_MESSAGE);
|
---|
| 186 | return;
|
---|
| 187 | }
|
---|
[4674] | 188 |
|
---|
[8633] | 189 | Collection<? extends PrimitiveId> toLoad = Utils.filter(primitives, unloadedHistoryPredicate);
|
---|
[4760] | 190 | if (!toLoad.isEmpty()) {
|
---|
| 191 | HistoryLoadTask task = new HistoryLoadTask();
|
---|
[6448] | 192 | for (PrimitiveId p : notNewPrimitives) {
|
---|
| 193 | task.add(p);
|
---|
| 194 | }
|
---|
[4760] | 195 | Main.worker.submit(task);
|
---|
| 196 | }
|
---|
[4408] | 197 |
|
---|
[4760] | 198 | Runnable r = new Runnable() {
|
---|
[4408] | 199 |
|
---|
[4760] | 200 | @Override
|
---|
| 201 | public void run() {
|
---|
| 202 | try {
|
---|
[6448] | 203 | for (PrimitiveId p : notNewPrimitives) {
|
---|
| 204 | final History h = HistoryDataSet.getInstance().getHistory(p);
|
---|
[4760] | 205 | if (h == null) {
|
---|
| 206 | continue;
|
---|
| 207 | }
|
---|
| 208 | SwingUtilities.invokeLater(new Runnable() {
|
---|
| 209 | @Override
|
---|
| 210 | public void run() {
|
---|
| 211 | show(h);
|
---|
| 212 | }
|
---|
| 213 | });
|
---|
| 214 | }
|
---|
[10212] | 215 | } catch (final RuntimeException e) {
|
---|
[4760] | 216 | BugReportExceptionHandler.handleException(e);
|
---|
| 217 | }
|
---|
| 218 | }
|
---|
| 219 | };
|
---|
| 220 | Main.worker.submit(r);
|
---|
| 221 | }
|
---|
[4408] | 222 |
|
---|
[6448] | 223 | private final Predicate<PrimitiveId> unloadedHistoryPredicate = new Predicate<PrimitiveId>() {
|
---|
[4408] | 224 |
|
---|
[8285] | 225 | private HistoryDataSet hds = HistoryDataSet.getInstance();
|
---|
[4408] | 226 |
|
---|
[4760] | 227 | @Override
|
---|
[6448] | 228 | public boolean evaluate(PrimitiveId p) {
|
---|
| 229 | History h = hds.getHistory(p);
|
---|
[6223] | 230 | if (h == null)
|
---|
[4760] | 231 | // reload if the history is not in the cache yet
|
---|
| 232 | return true;
|
---|
[7025] | 233 | else
|
---|
[4760] | 234 | // reload if the history object of the selected object is not in the cache yet
|
---|
[8345] | 235 | return !p.isNew() && h.getByVersion(p.getUniqueId()) == null;
|
---|
[4760] | 236 | }
|
---|
| 237 | };
|
---|
[4408] | 238 |
|
---|
[6448] | 239 | private final Predicate<PrimitiveId> notNewPredicate = new Predicate<PrimitiveId>() {
|
---|
[4408] | 240 |
|
---|
[4760] | 241 | @Override
|
---|
[6448] | 242 | public boolean evaluate(PrimitiveId p) {
|
---|
[4760] | 243 | return !p.isNew();
|
---|
| 244 | }
|
---|
| 245 | };
|
---|
[2512] | 246 | }
|
---|