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

Last change on this file was 18715, checked in by taylor.smock, 3 years ago

Fix #22798: Convert dialog actions which extend AbstractAction to ones which extend JosmAction

This will allow users to bind shortcuts to actions they make frequently, such as
creating new relations.

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