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

Last change on this file since 13014 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

  • 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.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.Main;
36import org.openstreetmap.josm.actions.AbstractInfoAction;
37import org.openstreetmap.josm.actions.downloadtasks.ChangesetHeaderDownloadTask;
38import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
39import org.openstreetmap.josm.data.osm.Changeset;
40import org.openstreetmap.josm.data.osm.ChangesetCache;
41import org.openstreetmap.josm.data.osm.DataSet;
42import org.openstreetmap.josm.data.osm.OsmPrimitive;
43import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
44import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
45import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
46import org.openstreetmap.josm.gui.MainApplication;
47import org.openstreetmap.josm.gui.SideButton;
48import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetCacheManager;
49import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetInSelectionListModel;
50import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListCellRenderer;
51import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListModel;
52import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetsInActiveDataLayerListModel;
53import org.openstreetmap.josm.gui.help.HelpUtil;
54import org.openstreetmap.josm.gui.io.CloseChangesetTask;
55import org.openstreetmap.josm.gui.layer.OsmDataLayer;
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.OnlineResource;
60import org.openstreetmap.josm.spi.preferences.Config;
61import org.openstreetmap.josm.tools.ImageProvider;
62import org.openstreetmap.josm.tools.Logging;
63import org.openstreetmap.josm.tools.OpenBrowser;
64import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
65
66/**
67 * ChangesetDialog is a toggle dialog which displays the current list of changesets.
68 * It either displays
69 * <ul>
70 * <li>the list of changesets the currently selected objects are assigned to</li>
71 * <li>the list of changesets objects in the current data layer are assigend to</li>
72 * </ul>
73 *
74 * The dialog offers actions to download and to close changesets. It can also launch an external
75 * browser with information about a changeset. Furthermore, it can select all objects in
76 * the current data layer being assigned to a specific changeset.
77 * @since 2613
78 */
79public class ChangesetDialog extends ToggleDialog {
80 private ChangesetInSelectionListModel inSelectionModel;
81 private ChangesetsInActiveDataLayerListModel inActiveDataLayerModel;
82 private JList<Changeset> lstInSelection;
83 private JList<Changeset> lstInActiveDataLayer;
84 private JCheckBox cbInSelectionOnly;
85 private JPanel pnlList;
86
87 // the actions
88 private SelectObjectsAction selectObjectsAction;
89 private ReadChangesetsAction readChangesetAction;
90 private ShowChangesetInfoAction showChangesetInfoAction;
91 private CloseOpenChangesetsAction closeChangesetAction;
92
93 private ChangesetDialogPopup popupMenu;
94
95 protected void buildChangesetsLists() {
96 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
97 inSelectionModel = new ChangesetInSelectionListModel(selectionModel);
98
99 lstInSelection = new JList<>(inSelectionModel);
100 lstInSelection.setSelectionModel(selectionModel);
101 lstInSelection.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
102 lstInSelection.setCellRenderer(new ChangesetListCellRenderer());
103
104 selectionModel = new DefaultListSelectionModel();
105 inActiveDataLayerModel = new ChangesetsInActiveDataLayerListModel(selectionModel);
106 lstInActiveDataLayer = new JList<>(inActiveDataLayerModel);
107 lstInActiveDataLayer.setSelectionModel(selectionModel);
108 lstInActiveDataLayer.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
109 lstInActiveDataLayer.setCellRenderer(new ChangesetListCellRenderer());
110
111 DblClickHandler dblClickHandler = new DblClickHandler();
112 lstInSelection.addMouseListener(dblClickHandler);
113 lstInActiveDataLayer.addMouseListener(dblClickHandler);
114 }
115
116 protected void registerAsListener() {
117 // let the model for changesets in the current selection listen to various events
118 ChangesetCache.getInstance().addChangesetCacheListener(inSelectionModel);
119 SelectionEventManager.getInstance().addSelectionListener(inSelectionModel);
120
121 // let the model for changesets in the current layer listen to various
122 // events and bootstrap it's content
123 ChangesetCache.getInstance().addChangesetCacheListener(inActiveDataLayerModel);
124 MainApplication.getLayerManager().addActiveLayerChangeListener(inActiveDataLayerModel);
125 OsmDataLayer editLayer = MainApplication.getLayerManager().getEditLayer();
126 if (editLayer != null) {
127 editLayer.data.addDataSetListener(inActiveDataLayerModel);
128 inActiveDataLayerModel.initFromDataSet(editLayer.data);
129 inSelectionModel.initFromPrimitives(editLayer.data.getAllSelected());
130 }
131 }
132
133 protected void unregisterAsListener() {
134 // remove the list model for the current edit layer as listener
135 //
136 ChangesetCache.getInstance().removeChangesetCacheListener(inActiveDataLayerModel);
137 MainApplication.getLayerManager().removeActiveLayerChangeListener(inActiveDataLayerModel);
138 OsmDataLayer editLayer = MainApplication.getLayerManager().getEditLayer();
139 if (editLayer != null) {
140 editLayer.data.removeDataSetListener(inActiveDataLayerModel);
141 }
142
143 // remove the list model for the changesets in the current selection as
144 // 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 protected void build() {
184 JPanel pnl = new JPanel(new BorderLayout());
185 pnl.add(buildFilterPanel(), BorderLayout.NORTH);
186 pnlList = buildListPanel();
187 pnl.add(pnlList, BorderLayout.CENTER);
188
189 cbInSelectionOnly.addItemListener(new FilterChangeHandler());
190
191 HelpUtil.setHelpContext(pnl, HelpUtil.ht("/Dialog/ChangesetList"));
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 OsmDataLayer editLayer = MainApplication.getLayerManager().getEditLayer();
238 if (editLayer != null) {
239 inSelectionModel.initFromPrimitives(editLayer.data.getAllSelected());
240 inActiveDataLayerModel.initFromDataSet(editLayer.data);
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 );
256 build();
257 initWithCurrentData();
258 }
259
260 class DblClickHandler extends MouseAdapter {
261 @Override
262 public void mouseClicked(MouseEvent e) {
263 if (!SwingUtilities.isLeftMouseButton(e) || e.getClickCount() < 2)
264 return;
265 Set<Integer> sel = getCurrentChangesetListModel().getSelectedChangesetIds();
266 if (sel.isEmpty())
267 return;
268 if (MainApplication.getLayerManager().getEditDataSet() == null)
269 return;
270 new SelectObjectsAction().selectObjectsByChangesetIds(MainApplication.getLayerManager().getEditDataSet(), sel);
271 }
272
273 }
274
275 class FilterChangeHandler implements ItemListener {
276 @Override
277 public void itemStateChanged(ItemEvent e) {
278 Config.getPref().putBoolean("changeset-dialog.for-selected-objects-only", cbInSelectionOnly.isSelected());
279 pnlList.removeAll();
280 if (cbInSelectionOnly.isSelected()) {
281 pnlList.add(new JScrollPane(lstInSelection), BorderLayout.CENTER);
282 } else {
283 pnlList.add(new JScrollPane(lstInActiveDataLayer), BorderLayout.CENTER);
284 }
285 validate();
286 repaint();
287 }
288 }
289
290 /**
291 * Selects objects for the currently selected changesets.
292 */
293 class SelectObjectsAction extends AbstractAction implements ListSelectionListener, ItemListener {
294
295 SelectObjectsAction() {
296 putValue(NAME, tr("Select"));
297 putValue(SHORT_DESCRIPTION, tr("Select all objects assigned to the currently selected changesets"));
298 new ImageProvider("dialogs", "select").getResource().attachImageIcon(this, true);
299 updateEnabledState();
300 }
301
302 public void selectObjectsByChangesetIds(DataSet ds, Set<Integer> ids) {
303 if (ds == null || ids == null)
304 return;
305 Set<OsmPrimitive> sel = new HashSet<>();
306 for (OsmPrimitive p: ds.allPrimitives()) {
307 if (ids.contains(p.getChangesetId())) {
308 sel.add(p);
309 }
310 }
311 ds.setSelected(sel);
312 }
313
314 @Override
315 public void actionPerformed(ActionEvent e) {
316 if (MainApplication.getLayerManager().getEditLayer() == null)
317 return;
318 ChangesetListModel model = getCurrentChangesetListModel();
319 Set<Integer> sel = model.getSelectedChangesetIds();
320 if (sel.isEmpty())
321 return;
322
323 DataSet ds = MainApplication.getLayerManager().getEditLayer().data;
324 selectObjectsByChangesetIds(ds, sel);
325 }
326
327 protected void updateEnabledState() {
328 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0);
329 }
330
331 @Override
332 public void itemStateChanged(ItemEvent e) {
333 updateEnabledState();
334
335 }
336
337 @Override
338 public void valueChanged(ListSelectionEvent e) {
339 updateEnabledState();
340 }
341 }
342
343 /**
344 * Downloads selected changesets
345 *
346 */
347 class ReadChangesetsAction extends AbstractAction implements ListSelectionListener, ItemListener {
348 ReadChangesetsAction() {
349 putValue(NAME, tr("Download"));
350 putValue(SHORT_DESCRIPTION, tr("Download information about the selected changesets from the OSM server"));
351 new ImageProvider("download").getResource().attachImageIcon(this, true);
352 updateEnabledState();
353 }
354
355 @Override
356 public void actionPerformed(ActionEvent e) {
357 ChangesetListModel model = getCurrentChangesetListModel();
358 Set<Integer> sel = model.getSelectedChangesetIds();
359 if (sel.isEmpty())
360 return;
361 ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(sel);
362 MainApplication.worker.submit(new PostDownloadHandler(task, task.download()));
363 }
364
365 protected void updateEnabledState() {
366 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0 && !Main.isOffline(OnlineResource.OSM_API));
367 }
368
369 @Override
370 public void itemStateChanged(ItemEvent e) {
371 updateEnabledState();
372 }
373
374 @Override
375 public void valueChanged(ListSelectionEvent e) {
376 updateEnabledState();
377 }
378 }
379
380 /**
381 * Closes the currently selected changesets
382 *
383 */
384 class CloseOpenChangesetsAction extends AbstractAction implements ListSelectionListener, ItemListener {
385 CloseOpenChangesetsAction() {
386 putValue(NAME, tr("Close open changesets"));
387 putValue(SHORT_DESCRIPTION, tr("Closes the selected open changesets"));
388 new ImageProvider("closechangeset").getResource().attachImageIcon(this, true);
389 updateEnabledState();
390 }
391
392 @Override
393 public void actionPerformed(ActionEvent e) {
394 List<Changeset> sel = getCurrentChangesetListModel().getSelectedOpenChangesets();
395 if (sel.isEmpty())
396 return;
397 MainApplication.worker.submit(new CloseChangesetTask(sel));
398 }
399
400 protected void updateEnabledState() {
401 setEnabled(getCurrentChangesetListModel().hasSelectedOpenChangesets());
402 }
403
404 @Override
405 public void itemStateChanged(ItemEvent e) {
406 updateEnabledState();
407 }
408
409 @Override
410 public void valueChanged(ListSelectionEvent e) {
411 updateEnabledState();
412 }
413 }
414
415 /**
416 * Show information about the currently selected changesets
417 *
418 */
419 class ShowChangesetInfoAction extends AbstractAction implements ListSelectionListener, ItemListener {
420 ShowChangesetInfoAction() {
421 putValue(NAME, tr("Show info"));
422 putValue(SHORT_DESCRIPTION, tr("Open a web page for each selected changeset"));
423 new ImageProvider("help/internet").getResource().attachImageIcon(this, true);
424 updateEnabledState();
425 }
426
427 @Override
428 public void actionPerformed(ActionEvent e) {
429 Set<Changeset> sel = getCurrentChangesetListModel().getSelectedChangesets();
430 if (sel.isEmpty())
431 return;
432 if (sel.size() > 10 && !AbstractInfoAction.confirmLaunchMultiple(sel.size()))
433 return;
434 String baseUrl = Main.getBaseBrowseUrl();
435 for (Changeset cs: sel) {
436 OpenBrowser.displayUrl(baseUrl + "/changeset/" + cs.getId());
437 }
438 }
439
440 protected void updateEnabledState() {
441 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0);
442 }
443
444 @Override
445 public void itemStateChanged(ItemEvent e) {
446 updateEnabledState();
447 }
448
449 @Override
450 public void valueChanged(ListSelectionEvent e) {
451 updateEnabledState();
452 }
453 }
454
455 /**
456 * Show information about the currently selected changesets
457 *
458 */
459 class LaunchChangesetManagerAction extends AbstractAction {
460 LaunchChangesetManagerAction() {
461 putValue(NAME, tr("Details"));
462 putValue(SHORT_DESCRIPTION, tr("Opens the Changeset Manager window for the selected changesets"));
463 new ImageProvider("dialogs/changeset", "changesetmanager").getResource().attachImageIcon(this, true);
464 }
465
466 @Override
467 public void actionPerformed(ActionEvent e) {
468 ChangesetListModel model = getCurrentChangesetListModel();
469 Set<Integer> sel = model.getSelectedChangesetIds();
470 LaunchChangesetManager.displayChangesets(sel);
471 }
472 }
473
474 /**
475 * A utility class to fetch changesets and display the changeset dialog.
476 */
477 public static final class LaunchChangesetManager {
478
479 private LaunchChangesetManager() {
480 // Hide implicit public constructor for utility classes
481 }
482
483 private static void launchChangesetManager(Collection<Integer> toSelect) {
484 ChangesetCacheManager cm = ChangesetCacheManager.getInstance();
485 if (cm.isVisible()) {
486 cm.setExtendedState(Frame.NORMAL);
487 cm.toFront();
488 cm.requestFocus();
489 } else {
490 cm.setVisible(true);
491 cm.toFront();
492 cm.requestFocus();
493 }
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 (!Main.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.