source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/ChangesetDialog.java@ 15889

Last change on this file since 15889 was 15706, checked in by simon04, 4 years ago

see #14465 -Tag2Link: combine links with same name and launch at once

  • Property svn:eol-style set to native
File size: 21.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.FlowLayout;
8import java.awt.Frame;
9import java.awt.event.ActionEvent;
10import java.awt.event.ItemEvent;
11import java.awt.event.ItemListener;
12import java.awt.event.MouseAdapter;
13import java.awt.event.MouseEvent;
14import java.util.Arrays;
15import java.util.Collection;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Set;
19import java.util.concurrent.ExecutionException;
20import java.util.concurrent.Future;
21
22import javax.swing.AbstractAction;
23import javax.swing.Action;
24import javax.swing.DefaultListSelectionModel;
25import javax.swing.JCheckBox;
26import javax.swing.JList;
27import javax.swing.JMenuItem;
28import javax.swing.JPanel;
29import javax.swing.JScrollPane;
30import javax.swing.ListSelectionModel;
31import javax.swing.SwingUtilities;
32import javax.swing.event.ListSelectionEvent;
33import javax.swing.event.ListSelectionListener;
34
35import org.openstreetmap.josm.actions.OpenBrowserAction;
36import org.openstreetmap.josm.actions.downloadtasks.ChangesetHeaderDownloadTask;
37import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
38import org.openstreetmap.josm.data.osm.Changeset;
39import org.openstreetmap.josm.data.osm.ChangesetCache;
40import org.openstreetmap.josm.data.osm.DataSet;
41import org.openstreetmap.josm.data.osm.OsmPrimitive;
42import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
43import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
44import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
45import org.openstreetmap.josm.gui.MainApplication;
46import org.openstreetmap.josm.gui.SideButton;
47import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetCacheManager;
48import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetInSelectionListModel;
49import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListCellRenderer;
50import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListModel;
51import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetsInActiveDataLayerListModel;
52import org.openstreetmap.josm.gui.help.HelpUtil;
53import org.openstreetmap.josm.gui.io.CloseChangesetTask;
54import org.openstreetmap.josm.gui.util.GuiHelper;
55import org.openstreetmap.josm.gui.widgets.ListPopupMenu;
56import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
57import org.openstreetmap.josm.io.NetworkManager;
58import org.openstreetmap.josm.io.OnlineResource;
59import org.openstreetmap.josm.spi.preferences.Config;
60import org.openstreetmap.josm.tools.ImageProvider;
61import org.openstreetmap.josm.tools.Logging;
62import org.openstreetmap.josm.tools.OpenBrowser;
63import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
64
65/**
66 * ChangesetDialog is a toggle dialog which displays the current list of changesets.
67 * It either displays
68 * <ul>
69 * <li>the list of changesets the currently selected objects are assigned to</li>
70 * <li>the list of changesets objects in the current data layer are assigend to</li>
71 * </ul>
72 *
73 * The dialog offers actions to download and to close changesets. It can also launch an external
74 * browser with information about a changeset. Furthermore, it can select all objects in
75 * the current data layer being assigned to a specific changeset.
76 * @since 2613
77 */
78public class ChangesetDialog extends ToggleDialog {
79 private ChangesetInSelectionListModel inSelectionModel;
80 private ChangesetsInActiveDataLayerListModel inActiveDataLayerModel;
81 private JList<Changeset> lstInSelection;
82 private JList<Changeset> lstInActiveDataLayer;
83 private JCheckBox cbInSelectionOnly;
84 private JPanel pnlList;
85
86 // the actions
87 private SelectObjectsAction selectObjectsAction;
88 private ReadChangesetsAction readChangesetAction;
89 private ShowChangesetInfoAction showChangesetInfoAction;
90 private CloseOpenChangesetsAction closeChangesetAction;
91
92 private ChangesetDialogPopup popupMenu;
93
94 protected void buildChangesetsLists() {
95 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
96 inSelectionModel = new ChangesetInSelectionListModel(selectionModel);
97
98 lstInSelection = new JList<>(inSelectionModel);
99 lstInSelection.setSelectionModel(selectionModel);
100 lstInSelection.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
101 lstInSelection.setCellRenderer(new ChangesetListCellRenderer());
102
103 selectionModel = new DefaultListSelectionModel();
104 inActiveDataLayerModel = new ChangesetsInActiveDataLayerListModel(selectionModel);
105 lstInActiveDataLayer = new JList<>(inActiveDataLayerModel);
106 lstInActiveDataLayer.setSelectionModel(selectionModel);
107 lstInActiveDataLayer.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
108 lstInActiveDataLayer.setCellRenderer(new ChangesetListCellRenderer());
109
110 DblClickHandler dblClickHandler = new DblClickHandler();
111 lstInSelection.addMouseListener(dblClickHandler);
112 lstInActiveDataLayer.addMouseListener(dblClickHandler);
113 }
114
115 protected void registerAsListener() {
116 // let the model for changesets in the current selection listen to various events
117 ChangesetCache.getInstance().addChangesetCacheListener(inSelectionModel);
118 SelectionEventManager.getInstance().addSelectionListener(inSelectionModel);
119
120 // let the model for changesets in the current layer listen to various
121 // events and bootstrap it's content
122 ChangesetCache.getInstance().addChangesetCacheListener(inActiveDataLayerModel);
123 MainApplication.getLayerManager().addActiveLayerChangeListener(inActiveDataLayerModel);
124 DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
125 if (ds != null) {
126 ds.addDataSetListener(inActiveDataLayerModel);
127 inActiveDataLayerModel.initFromDataSet(ds);
128 inSelectionModel.initFromPrimitives(ds.getAllSelected());
129 }
130 }
131
132 protected void unregisterAsListener() {
133 // remove the list model for the current edit layer as listener
134 ChangesetCache.getInstance().removeChangesetCacheListener(inActiveDataLayerModel);
135 MainApplication.getLayerManager().removeActiveLayerChangeListener(inActiveDataLayerModel);
136 DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
137 if (ds != null) {
138 ds.removeDataSetListener(inActiveDataLayerModel);
139 }
140
141 // remove the list model for the changesets in the current selection as listener
142 SelectionEventManager.getInstance().removeSelectionListener(inSelectionModel);
143 ChangesetCache.getInstance().removeChangesetCacheListener(inSelectionModel);
144 }
145
146 @Override
147 public void showNotify() {
148 registerAsListener();
149 DatasetEventManager.getInstance().addDatasetListener(inActiveDataLayerModel, FireMode.IN_EDT);
150 }
151
152 @Override
153 public void hideNotify() {
154 unregisterAsListener();
155 DatasetEventManager.getInstance().removeDatasetListener(inActiveDataLayerModel);
156 }
157
158 protected JPanel buildFilterPanel() {
159 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
160 pnl.setBorder(null);
161 cbInSelectionOnly = new JCheckBox(tr("For selected objects only"));
162 pnl.add(cbInSelectionOnly);
163 cbInSelectionOnly.setToolTipText(tr("<html>Select to show changesets for the currently selected objects only.<br>"
164 + "Unselect to show all changesets for objects in the current data layer.</html>"));
165 cbInSelectionOnly.setSelected(Config.getPref().getBoolean("changeset-dialog.for-selected-objects-only", false));
166 return pnl;
167 }
168
169 protected JPanel buildListPanel() {
170 buildChangesetsLists();
171 JPanel pnl = new JPanel(new BorderLayout());
172 if (cbInSelectionOnly.isSelected()) {
173 pnl.add(new JScrollPane(lstInSelection));
174 } else {
175 pnl.add(new JScrollPane(lstInActiveDataLayer));
176 }
177 return pnl;
178 }
179
180 @Override
181 public String helpTopic() {
182 return HelpUtil.ht("/Dialog/ChangesetList");
183 }
184
185 protected void build() {
186 JPanel pnl = new JPanel(new BorderLayout());
187 pnl.add(buildFilterPanel(), BorderLayout.NORTH);
188 pnlList = buildListPanel();
189 pnl.add(pnlList, BorderLayout.CENTER);
190
191 cbInSelectionOnly.addItemListener(new FilterChangeHandler());
192
193 // -- select objects action
194 selectObjectsAction = new SelectObjectsAction();
195 cbInSelectionOnly.addItemListener(selectObjectsAction);
196
197 // -- read changesets action
198 readChangesetAction = new ReadChangesetsAction();
199 cbInSelectionOnly.addItemListener(readChangesetAction);
200
201 // -- close changesets action
202 closeChangesetAction = new CloseOpenChangesetsAction();
203 cbInSelectionOnly.addItemListener(closeChangesetAction);
204
205 // -- show info action
206 showChangesetInfoAction = new ShowChangesetInfoAction();
207 cbInSelectionOnly.addItemListener(showChangesetInfoAction);
208
209 popupMenu = new ChangesetDialogPopup(lstInActiveDataLayer, lstInSelection);
210
211 PopupMenuLauncher popupMenuLauncher = new PopupMenuLauncher(popupMenu);
212 lstInSelection.addMouseListener(popupMenuLauncher);
213 lstInActiveDataLayer.addMouseListener(popupMenuLauncher);
214
215 createLayout(pnl, false, Arrays.asList(
216 new SideButton(selectObjectsAction, false),
217 new SideButton(readChangesetAction, false),
218 new SideButton(closeChangesetAction, false),
219 new SideButton(showChangesetInfoAction, false),
220 new SideButton(new LaunchChangesetManagerAction(), false)
221 ));
222 }
223
224 protected JList<Changeset> getCurrentChangesetList() {
225 if (cbInSelectionOnly.isSelected())
226 return lstInSelection;
227 return lstInActiveDataLayer;
228 }
229
230 protected ChangesetListModel getCurrentChangesetListModel() {
231 if (cbInSelectionOnly.isSelected())
232 return inSelectionModel;
233 return inActiveDataLayerModel;
234 }
235
236 protected void initWithCurrentData() {
237 DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
238 if (ds != null) {
239 inSelectionModel.initFromPrimitives(ds.getAllSelected());
240 inActiveDataLayerModel.initFromDataSet(ds);
241 }
242 }
243
244 /**
245 * Constructs a new {@code ChangesetDialog}.
246 */
247 public ChangesetDialog() {
248 super(
249 tr("Changesets"),
250 "changesetdialog",
251 tr("Open the list of changesets in the current layer."),
252 null, /* no keyboard shortcut */
253 200, /* the preferred height */
254 false, /* don't show if there is no preference */
255 null /* no preferences settings */,
256 true /* expert only */
257 );
258 build();
259 initWithCurrentData();
260 }
261
262 class DblClickHandler extends MouseAdapter {
263 @Override
264 public void mouseClicked(MouseEvent e) {
265 if (!SwingUtilities.isLeftMouseButton(e) || e.getClickCount() < 2)
266 return;
267 Set<Integer> sel = getCurrentChangesetListModel().getSelectedChangesetIds();
268 if (sel.isEmpty())
269 return;
270 if (MainApplication.getLayerManager().getActiveDataSet() == null)
271 return;
272 new SelectObjectsAction().selectObjectsByChangesetIds(MainApplication.getLayerManager().getActiveDataSet(), sel);
273 }
274
275 }
276
277 class FilterChangeHandler implements ItemListener {
278 @Override
279 public void itemStateChanged(ItemEvent e) {
280 Config.getPref().putBoolean("changeset-dialog.for-selected-objects-only", cbInSelectionOnly.isSelected());
281 pnlList.removeAll();
282 if (cbInSelectionOnly.isSelected()) {
283 pnlList.add(new JScrollPane(lstInSelection), BorderLayout.CENTER);
284 } else {
285 pnlList.add(new JScrollPane(lstInActiveDataLayer), BorderLayout.CENTER);
286 }
287 validate();
288 repaint();
289 }
290 }
291
292 /**
293 * Selects objects for the currently selected changesets.
294 */
295 class SelectObjectsAction extends AbstractAction implements ListSelectionListener, ItemListener {
296
297 SelectObjectsAction() {
298 putValue(NAME, tr("Select"));
299 putValue(SHORT_DESCRIPTION, tr("Select all objects assigned to the currently selected changesets"));
300 new ImageProvider("dialogs", "select").getResource().attachImageIcon(this, true);
301 updateEnabledState();
302 }
303
304 public void selectObjectsByChangesetIds(DataSet ds, Set<Integer> ids) {
305 if (ds == null || ids == null)
306 return;
307 Set<OsmPrimitive> sel = new HashSet<>();
308 for (OsmPrimitive p: ds.allPrimitives()) {
309 if (ids.contains(p.getChangesetId())) {
310 sel.add(p);
311 }
312 }
313 ds.setSelected(sel);
314 }
315
316 @Override
317 public void actionPerformed(ActionEvent e) {
318 DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
319 if (ds == null)
320 return;
321 ChangesetListModel model = getCurrentChangesetListModel();
322 Set<Integer> sel = model.getSelectedChangesetIds();
323 if (sel.isEmpty())
324 return;
325
326 selectObjectsByChangesetIds(ds, sel);
327 }
328
329 protected void updateEnabledState() {
330 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0);
331 }
332
333 @Override
334 public void itemStateChanged(ItemEvent e) {
335 updateEnabledState();
336
337 }
338
339 @Override
340 public void valueChanged(ListSelectionEvent e) {
341 updateEnabledState();
342 }
343 }
344
345 /**
346 * Downloads selected changesets
347 *
348 */
349 class ReadChangesetsAction extends AbstractAction implements ListSelectionListener, ItemListener {
350 ReadChangesetsAction() {
351 putValue(NAME, tr("Download"));
352 putValue(SHORT_DESCRIPTION, tr("Download information about the selected changesets from the OSM server"));
353 new ImageProvider("download").getResource().attachImageIcon(this, true);
354 updateEnabledState();
355 }
356
357 @Override
358 public void actionPerformed(ActionEvent e) {
359 ChangesetListModel model = getCurrentChangesetListModel();
360 Set<Integer> sel = model.getSelectedChangesetIds();
361 if (sel.isEmpty())
362 return;
363 ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(sel);
364 MainApplication.worker.submit(new PostDownloadHandler(task, task.download()));
365 }
366
367 protected void updateEnabledState() {
368 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0 && !NetworkManager.isOffline(OnlineResource.OSM_API));
369 }
370
371 @Override
372 public void itemStateChanged(ItemEvent e) {
373 updateEnabledState();
374 }
375
376 @Override
377 public void valueChanged(ListSelectionEvent e) {
378 updateEnabledState();
379 }
380 }
381
382 /**
383 * Closes the currently selected changesets
384 *
385 */
386 class CloseOpenChangesetsAction extends AbstractAction implements ListSelectionListener, ItemListener {
387 CloseOpenChangesetsAction() {
388 putValue(NAME, tr("Close open changesets"));
389 putValue(SHORT_DESCRIPTION, tr("Close the selected open changesets"));
390 new ImageProvider("closechangeset").getResource().attachImageIcon(this, true);
391 updateEnabledState();
392 }
393
394 @Override
395 public void actionPerformed(ActionEvent e) {
396 List<Changeset> sel = getCurrentChangesetListModel().getSelectedOpenChangesets();
397 if (sel.isEmpty())
398 return;
399 MainApplication.worker.submit(new CloseChangesetTask(sel));
400 }
401
402 protected void updateEnabledState() {
403 setEnabled(getCurrentChangesetListModel().hasSelectedOpenChangesets());
404 }
405
406 @Override
407 public void itemStateChanged(ItemEvent e) {
408 updateEnabledState();
409 }
410
411 @Override
412 public void valueChanged(ListSelectionEvent e) {
413 updateEnabledState();
414 }
415 }
416
417 /**
418 * Show information about the currently selected changesets
419 *
420 */
421 class ShowChangesetInfoAction extends AbstractAction implements ListSelectionListener, ItemListener {
422 ShowChangesetInfoAction() {
423 putValue(NAME, tr("Show info"));
424 putValue(SHORT_DESCRIPTION, tr("Open a web page for each selected changeset"));
425 new ImageProvider("help/internet").getResource().attachImageIcon(this, true);
426 updateEnabledState();
427 }
428
429 @Override
430 public void actionPerformed(ActionEvent e) {
431 Set<Changeset> sel = getCurrentChangesetListModel().getSelectedChangesets();
432 if (sel.isEmpty())
433 return;
434 if (sel.size() > 10 && !OpenBrowserAction.confirmLaunchMultiple(sel.size()))
435 return;
436 String baseUrl = Config.getUrls().getBaseBrowseUrl();
437 for (Changeset cs: sel) {
438 OpenBrowser.displayUrl(baseUrl + "/changeset/" + cs.getId());
439 }
440 }
441
442 protected void updateEnabledState() {
443 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0);
444 }
445
446 @Override
447 public void itemStateChanged(ItemEvent e) {
448 updateEnabledState();
449 }
450
451 @Override
452 public void valueChanged(ListSelectionEvent e) {
453 updateEnabledState();
454 }
455 }
456
457 /**
458 * Show information about the currently selected changesets
459 *
460 */
461 class LaunchChangesetManagerAction extends AbstractAction {
462 LaunchChangesetManagerAction() {
463 putValue(NAME, tr("Details"));
464 putValue(SHORT_DESCRIPTION, tr("Opens the Changeset Manager window for the selected changesets"));
465 new ImageProvider("dialogs/changeset", "changesetmanager").getResource().attachImageIcon(this, true);
466 }
467
468 @Override
469 public void actionPerformed(ActionEvent e) {
470 ChangesetListModel model = getCurrentChangesetListModel();
471 Set<Integer> sel = model.getSelectedChangesetIds();
472 LaunchChangesetManager.displayChangesets(sel);
473 }
474 }
475
476 /**
477 * A utility class to fetch changesets and display the changeset dialog.
478 */
479 public static final class LaunchChangesetManager {
480
481 private LaunchChangesetManager() {
482 // Hide implicit public constructor for utility classes
483 }
484
485 private static void launchChangesetManager(Collection<Integer> toSelect) {
486 ChangesetCacheManager cm = ChangesetCacheManager.getInstance();
487 if (cm.isVisible()) {
488 cm.setExtendedState(Frame.NORMAL);
489 } else {
490 cm.setVisible(true);
491 }
492 cm.toFront();
493 cm.setSelectedChangesetsById(toSelect);
494 }
495
496 /**
497 * Fetches changesets and display the changeset dialog.
498 * @param sel the changeset ids to fetch and display.
499 */
500 public static void displayChangesets(final Set<Integer> sel) {
501 final Set<Integer> toDownload = new HashSet<>();
502 if (!NetworkManager.isOffline(OnlineResource.OSM_API)) {
503 ChangesetCache cc = ChangesetCache.getInstance();
504 for (int id: sel) {
505 if (!cc.contains(id)) {
506 toDownload.add(id);
507 }
508 }
509 }
510
511 final ChangesetHeaderDownloadTask task;
512 final Future<?> future;
513 if (toDownload.isEmpty()) {
514 task = null;
515 future = null;
516 } else {
517 task = new ChangesetHeaderDownloadTask(toDownload);
518 future = MainApplication.worker.submit(new PostDownloadHandler(task, task.download()));
519 }
520
521 Runnable r = () -> {
522 // first, wait for the download task to finish, if a download task was launched
523 if (future != null) {
524 try {
525 future.get();
526 } catch (InterruptedException e1) {
527 Logging.log(Logging.LEVEL_WARN, "InterruptedException in ChangesetDialog while downloading changeset header", e1);
528 Thread.currentThread().interrupt();
529 } catch (ExecutionException e2) {
530 Logging.error(e2);
531 BugReportExceptionHandler.handleException(e2.getCause());
532 return;
533 }
534 }
535 if (task != null) {
536 if (task.isCanceled())
537 // don't launch the changeset manager if the download task was canceled
538 return;
539 if (task.isFailed()) {
540 toDownload.clear();
541 }
542 }
543 // launch the task
544 GuiHelper.runInEDT(() -> launchChangesetManager(sel));
545 };
546 MainApplication.worker.submit(r);
547 }
548 }
549
550 class ChangesetDialogPopup extends ListPopupMenu {
551 ChangesetDialogPopup(JList<?>... lists) {
552 super(lists);
553 add(selectObjectsAction);
554 addSeparator();
555 add(readChangesetAction);
556 add(closeChangesetAction);
557 addSeparator();
558 add(showChangesetInfoAction);
559 }
560 }
561
562 /**
563 * Add a separator to the popup menu
564 */
565 public void addPopupMenuSeparator() {
566 popupMenu.addSeparator();
567 }
568
569 /**
570 * Add a menu item to the popup menu
571 * @param a The action to add
572 * @return The menu item that was added.
573 */
574 public JMenuItem addPopupMenuAction(Action a) {
575 return popupMenu.add(a);
576 }
577}
Note: See TracBrowser for help on using the repository browser.