source: josm/trunk/src/org/openstreetmap/josm/gui/download/BookmarkList.java

Last change on this file was 17721, checked in by simon04, 3 years ago

see #14176 - Fix BookmarkList.ChangesetBookmark

https://errorprone.info/bugpattern/FormatString

  • Property svn:eol-style set to native
File size: 14.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.download;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.GraphicsEnvironment;
8import java.time.ZoneId;
9import java.util.ArrayList;
10import java.util.Arrays;
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Comparator;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Locale;
17import java.util.Objects;
18
19import javax.swing.DefaultListModel;
20import javax.swing.ImageIcon;
21import javax.swing.JLabel;
22import javax.swing.JList;
23import javax.swing.ListCellRenderer;
24import javax.swing.UIManager;
25
26import org.openstreetmap.josm.actions.downloadtasks.ChangesetQueryTask;
27import org.openstreetmap.josm.data.Bounds;
28import org.openstreetmap.josm.data.UserIdentityManager;
29import org.openstreetmap.josm.data.coor.LatLon;
30import org.openstreetmap.josm.data.osm.Changeset;
31import org.openstreetmap.josm.data.osm.UserInfo;
32import org.openstreetmap.josm.data.preferences.IntegerProperty;
33import org.openstreetmap.josm.data.projection.Projection;
34import org.openstreetmap.josm.data.projection.Projections;
35import org.openstreetmap.josm.gui.MainApplication;
36import org.openstreetmap.josm.gui.MapViewState;
37import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetCacheManager;
38import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
39import org.openstreetmap.josm.gui.util.GuiHelper;
40import org.openstreetmap.josm.io.ChangesetQuery;
41import org.openstreetmap.josm.spi.preferences.Config;
42import org.openstreetmap.josm.tools.ImageProvider;
43import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
44import org.openstreetmap.josm.tools.Logging;
45
46/**
47 * List class that read and save its content from the bookmark file.
48 * @since 6340
49 */
50public class BookmarkList extends JList<BookmarkList.Bookmark> {
51
52 /**
53 * The maximum number of changeset bookmarks to maintain in list.
54 * @since 12495
55 */
56 public static final IntegerProperty MAX_CHANGESET_BOOKMARKS = new IntegerProperty("bookmarks.changesets.max-entries", 15);
57
58 /**
59 * Class holding one bookmarkentry.
60 */
61 public static class Bookmark implements Comparable<Bookmark> {
62 private String name;
63 private Bounds area;
64 private ImageIcon icon;
65
66 /**
67 * Constructs a new {@code Bookmark} with the given contents.
68 * @param list Bookmark contents as a list of 5 elements.
69 * First item is the name, then come bounds arguments (minlat, minlon, maxlat, maxlon)
70 * @throws NumberFormatException if the bounds arguments are not numbers
71 * @throws IllegalArgumentException if list contain less than 5 elements
72 */
73 public Bookmark(Collection<String> list) {
74 List<String> array = new ArrayList<>(list);
75 if (array.size() < 5)
76 throw new IllegalArgumentException(tr("Wrong number of arguments for bookmark"));
77 icon = getDefaultIcon();
78 name = array.get(0);
79 area = new Bounds(Double.parseDouble(array.get(1)), Double.parseDouble(array.get(2)),
80 Double.parseDouble(array.get(3)), Double.parseDouble(array.get(4)));
81 }
82
83 /**
84 * Constructs a new empty {@code Bookmark}.
85 */
86 public Bookmark() {
87 this(null, null);
88 }
89
90 /**
91 * Constructs a new unnamed {@code Bookmark} for the given area.
92 * @param area The bookmark area
93 */
94 public Bookmark(Bounds area) {
95 this(null, area);
96 }
97
98 /**
99 * Constructs a new {@code Bookmark} for the given name and area.
100 * @param name The bookmark name
101 * @param area The bookmark area
102 * @since 12495
103 */
104 protected Bookmark(String name, Bounds area) {
105 this.icon = getDefaultIcon();
106 this.name = name;
107 this.area = area;
108 }
109
110 static ImageIcon getDefaultIcon() {
111 return ImageProvider.get("dialogs", "bookmark", ImageSizes.SMALLICON);
112 }
113
114 @Override
115 public String toString() {
116 return name;
117 }
118
119 @Override
120 public int compareTo(Bookmark b) {
121 return name.toLowerCase(Locale.ENGLISH).compareTo(b.name.toLowerCase(Locale.ENGLISH));
122 }
123
124 @Override
125 public int hashCode() {
126 return Objects.hash(name, area);
127 }
128
129 @Override
130 public boolean equals(Object obj) {
131 if (this == obj) return true;
132 if (obj == null || getClass() != obj.getClass()) return false;
133 Bookmark bookmark = (Bookmark) obj;
134 return Objects.equals(name, bookmark.name) &&
135 Objects.equals(area, bookmark.area);
136 }
137
138 /**
139 * Returns the bookmark area
140 * @return The bookmark area
141 */
142 public Bounds getArea() {
143 return area;
144 }
145
146 /**
147 * Returns the bookmark name
148 * @return The bookmark name
149 */
150 public String getName() {
151 return name;
152 }
153
154 /**
155 * Sets the bookmark name
156 * @param name The bookmark name
157 */
158 public void setName(String name) {
159 this.name = name;
160 }
161
162 /**
163 * Sets the bookmark area
164 * @param area The bookmark area
165 */
166 public void setArea(Bounds area) {
167 this.area = area;
168 }
169
170 /**
171 * Returns the bookmark icon.
172 * @return the bookmark icon
173 * @since 12495
174 */
175 public ImageIcon getIcon() {
176 return icon;
177 }
178
179 /**
180 * Sets the bookmark icon.
181 * @param icon the bookmark icon
182 * @since 12495
183 */
184 public void setIcon(ImageIcon icon) {
185 this.icon = icon;
186 }
187 }
188
189 /**
190 * A specific optional bookmark for the "home location" configured on osm.org website.
191 * @since 12495
192 */
193 public static class HomeLocationBookmark extends Bookmark {
194 /**
195 * Constructs a new {@code HomeLocationBookmark}.
196 */
197 public HomeLocationBookmark() {
198 setName(tr("Home location"));
199 setIcon(ImageProvider.get("help", "home", ImageSizes.SMALLICON));
200 UserInfo info = UserIdentityManager.getInstance().getUserInfo();
201 if (info == null) {
202 throw new IllegalStateException("User not identified");
203 }
204 LatLon home = info.getHome();
205 if (home == null) {
206 throw new IllegalStateException("User home location not set");
207 }
208 int zoom = info.getHomeZoom();
209 if (zoom <= 3) {
210 // 3 is the default zoom level in OSM database, but the real zoom level was not correct
211 // for a long time, see https://github.com/openstreetmap/openstreetmap-website/issues/1592
212 zoom = 15;
213 }
214 Projection mercator = Projections.getProjectionByCode("EPSG:3857");
215 setArea(MapViewState.createDefaultState(430, 400) // Size of map on osm.org user profile settings
216 .usingProjection(mercator)
217 .usingScale(Selector.GeneralSelector.level2scale(zoom) / 100)
218 .usingCenter(mercator.latlon2eastNorth(home))
219 .getViewArea()
220 .getLatLonBoundsBox());
221 }
222 }
223
224 /**
225 * A specific optional bookmark for the boundaries of recent changesets.
226 * @since 12495
227 */
228 public static class ChangesetBookmark extends Bookmark {
229 /**
230 * Constructs a new {@code ChangesetBookmark}.
231 * @param cs changeset from which the boundaries are read. Its id, name and comment are used to name the bookmark
232 */
233 public ChangesetBookmark(Changeset cs) {
234 setName(String.format("%d - %tF - %s", cs.getId(), cs.getCreatedAt().atZone(ZoneId.systemDefault()), cs.getComment()));
235 setIcon(ImageProvider.get("data", "changeset", ImageSizes.SMALLICON));
236 setArea(cs.getBounds());
237 }
238 }
239
240 /**
241 * Creates a bookmark list as well as the Buttons add and remove.
242 */
243 public BookmarkList() {
244 setModel(new DefaultListModel<Bookmark>());
245 load();
246 setVisibleRowCount(7);
247 setCellRenderer(new BookmarkCellRenderer());
248 }
249
250 /**
251 * Loads the home location bookmark from OSM API,
252 * the manual bookmarks from preferences file,
253 * the changeset bookmarks from changeset cache.
254 */
255 public final void load() {
256 final DefaultListModel<Bookmark> model = (DefaultListModel<Bookmark>) getModel();
257 model.removeAllElements();
258 UserIdentityManager im = UserIdentityManager.getInstance();
259 // Add home location bookmark first, if user fully identified
260 if (im.isFullyIdentified()) {
261 try {
262 model.addElement(new HomeLocationBookmark());
263 } catch (IllegalStateException e) {
264 Logging.info(e.getMessage());
265 Logging.trace(e);
266 }
267 }
268 // Then add manual bookmarks previously saved in local preferences
269 List<List<String>> args = Config.getPref().getListOfLists("bookmarks", null);
270 if (args != null) {
271 List<Bookmark> bookmarks = new LinkedList<>();
272 for (Collection<String> entry : args) {
273 try {
274 bookmarks.add(new Bookmark(entry));
275 } catch (IllegalArgumentException e) {
276 Logging.log(Logging.LEVEL_ERROR, tr("Error reading bookmark entry: %s", e.getMessage()), e);
277 }
278 }
279 Collections.sort(bookmarks);
280 for (Bookmark b : bookmarks) {
281 model.addElement(b);
282 }
283 }
284 // Finally add recent changeset bookmarks, if user name is known
285 final int n = MAX_CHANGESET_BOOKMARKS.get();
286 if (n > 0 && !im.isAnonymous()) {
287 final UserInfo userInfo = im.getUserInfo();
288 if (userInfo != null) {
289 final ChangesetCacheManager ccm = ChangesetCacheManager.getInstance();
290 final int userId = userInfo.getId();
291 int found = 0;
292 for (int i = 0; i < ccm.getModel().getRowCount() && found < n; i++) {
293 Changeset cs = ccm.getModel().getValueAt(i, 0);
294 if (cs.getUser().getId() == userId && cs.getBounds() != null) {
295 model.addElement(new ChangesetBookmark(cs));
296 found++;
297 }
298 }
299 }
300 }
301 }
302
303 /**
304 * Saves all manual bookmarks to the preferences file.
305 */
306 public final void save() {
307 List<List<String>> coll = new LinkedList<>();
308 for (Object o : ((DefaultListModel<Bookmark>) getModel()).toArray()) {
309 if (o instanceof HomeLocationBookmark || o instanceof ChangesetBookmark) {
310 continue;
311 }
312 String[] array = new String[5];
313 Bookmark b = (Bookmark) o;
314 array[0] = b.getName();
315 Bounds area = b.getArea();
316 array[1] = String.valueOf(area.getMinLat());
317 array[2] = String.valueOf(area.getMinLon());
318 array[3] = String.valueOf(area.getMaxLat());
319 array[4] = String.valueOf(area.getMaxLon());
320 coll.add(Arrays.asList(array));
321 }
322 Config.getPref().putListOfLists("bookmarks", coll);
323 }
324
325 /**
326 * Refreshes the changeset bookmarks.
327 * @since 12495
328 */
329 public void refreshChangesetBookmarks() {
330 final int n = MAX_CHANGESET_BOOKMARKS.get();
331 if (n > 0) {
332 final DefaultListModel<Bookmark> model = (DefaultListModel<Bookmark>) getModel();
333 for (int i = model.getSize() - 1; i >= 0; i--) {
334 if (model.get(i) instanceof ChangesetBookmark) {
335 model.remove(i);
336 }
337 }
338 ChangesetQuery query = ChangesetQuery.forCurrentUser();
339 if (!GraphicsEnvironment.isHeadless()) {
340 final ChangesetQueryTask task = new ChangesetQueryTask(this, query);
341 ChangesetCacheManager.getInstance().runDownloadTask(task);
342 MainApplication.worker.submit(() -> {
343 if (task.isCanceled() || task.isFailed())
344 return;
345 GuiHelper.runInEDT(() -> task.getDownloadedData().stream()
346 .filter(cs -> cs.getBounds() != null)
347 .sorted(Comparator.reverseOrder())
348 .limit(n)
349 .forEachOrdered(cs -> model.addElement(new ChangesetBookmark(cs))));
350 });
351 }
352 }
353 }
354
355 static class BookmarkCellRenderer extends JLabel implements ListCellRenderer<BookmarkList.Bookmark> {
356
357 /**
358 * Constructs a new {@code BookmarkCellRenderer}.
359 */
360 BookmarkCellRenderer() {
361 setOpaque(true);
362 }
363
364 protected void renderColor(boolean selected) {
365 if (selected) {
366 setBackground(UIManager.getColor("List.selectionBackground"));
367 setForeground(UIManager.getColor("List.selectionForeground"));
368 } else {
369 setBackground(UIManager.getColor("List.background"));
370 setForeground(UIManager.getColor("List.foreground"));
371 }
372 }
373
374 protected String buildToolTipText(Bookmark b) {
375 Bounds area = b.getArea();
376 StringBuilder sb = new StringBuilder(128);
377 if (area != null) {
378 sb.append("<html>min[latitude,longitude]=<strong>[")
379 .append(area.getMinLat()).append(',').append(area.getMinLon()).append("]</strong>"+
380 "<br>max[latitude,longitude]=<strong>[")
381 .append(area.getMaxLat()).append(',').append(area.getMaxLon()).append("]</strong>"+
382 "</html>");
383 }
384 return sb.toString();
385 }
386
387 @Override
388 public Component getListCellRendererComponent(JList<? extends Bookmark> list, Bookmark value, int index, boolean isSelected,
389 boolean cellHasFocus) {
390 renderColor(isSelected);
391 setIcon(value.getIcon());
392 setText(value.getName());
393 setToolTipText(buildToolTipText(value));
394 return this;
395 }
396 }
397}
Note: See TracBrowser for help on using the repository browser.