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

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

see #19334 - javadoc fixes + protected constructors for abstract classes

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