source: josm/trunk/src/org/openstreetmap/josm/gui/history/HistoryBrowserDialogManager.java@ 16123

Last change on this file since 16123 was 16123, checked in by Don-vip, 4 years ago

fix #18918 - Enable Ctrl-H shortcut to display history of primitives selected in changeset manager

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