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

Last change on this file since 17318 was 17188, checked in by Klumbumbus, 4 years ago

fix #19851 - Fix shortcut names

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