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

Last change on this file since 12634 was 12634, checked in by Don-vip, 7 years ago

see #15182 - deprecate Main.worker, replace it by gui.MainApplication.worker + code refactoring to make sure only editor packages use it

  • 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.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.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 Main.getLayerManager().addActiveLayerChangeListener(inActiveDataLayerModel);
124 OsmDataLayer editLayer = Main.getLayerManager().getEditLayer();
125 if (editLayer != null) {
126 editLayer.data.addDataSetListener(inActiveDataLayerModel);
127 inActiveDataLayerModel.initFromDataSet(editLayer.data);
128 inSelectionModel.initFromPrimitives(editLayer.data.getAllSelected());
129 }
130 }
131
132 protected void unregisterAsListener() {
133 // remove the list model for the current edit layer as listener
134 //
135 ChangesetCache.getInstance().removeChangesetCacheListener(inActiveDataLayerModel);
136 Main.getLayerManager().removeActiveLayerChangeListener(inActiveDataLayerModel);
137 OsmDataLayer editLayer = Main.getLayerManager().getEditLayer();
138 if (editLayer != null) {
139 editLayer.data.removeDataSetListener(inActiveDataLayerModel);
140 }
141
142 // remove the list model for the changesets in the current selection as
143 // 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(Main.pref.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 protected void build() {
183 JPanel pnl = new JPanel(new BorderLayout());
184 pnl.add(buildFilterPanel(), BorderLayout.NORTH);
185 pnlList = buildListPanel();
186 pnl.add(pnlList, BorderLayout.CENTER);
187
188 cbInSelectionOnly.addItemListener(new FilterChangeHandler());
189
190 HelpUtil.setHelpContext(pnl, HelpUtil.ht("/Dialog/ChangesetList"));
191
192 // -- select objects action
193 selectObjectsAction = new SelectObjectsAction();
194 cbInSelectionOnly.addItemListener(selectObjectsAction);
195
196 // -- read changesets action
197 readChangesetAction = new ReadChangesetsAction();
198 cbInSelectionOnly.addItemListener(readChangesetAction);
199
200 // -- close changesets action
201 closeChangesetAction = new CloseOpenChangesetsAction();
202 cbInSelectionOnly.addItemListener(closeChangesetAction);
203
204 // -- show info action
205 showChangesetInfoAction = new ShowChangesetInfoAction();
206 cbInSelectionOnly.addItemListener(showChangesetInfoAction);
207
208 popupMenu = new ChangesetDialogPopup(lstInActiveDataLayer, lstInSelection);
209
210 PopupMenuLauncher popupMenuLauncher = new PopupMenuLauncher(popupMenu);
211 lstInSelection.addMouseListener(popupMenuLauncher);
212 lstInActiveDataLayer.addMouseListener(popupMenuLauncher);
213
214 createLayout(pnl, false, Arrays.asList(
215 new SideButton(selectObjectsAction, false),
216 new SideButton(readChangesetAction, false),
217 new SideButton(closeChangesetAction, false),
218 new SideButton(showChangesetInfoAction, false),
219 new SideButton(new LaunchChangesetManagerAction(), false)
220 ));
221 }
222
223 protected JList<Changeset> getCurrentChangesetList() {
224 if (cbInSelectionOnly.isSelected())
225 return lstInSelection;
226 return lstInActiveDataLayer;
227 }
228
229 protected ChangesetListModel getCurrentChangesetListModel() {
230 if (cbInSelectionOnly.isSelected())
231 return inSelectionModel;
232 return inActiveDataLayerModel;
233 }
234
235 protected void initWithCurrentData() {
236 OsmDataLayer editLayer = Main.getLayerManager().getEditLayer();
237 if (editLayer != null) {
238 inSelectionModel.initFromPrimitives(editLayer.data.getAllSelected());
239 inActiveDataLayerModel.initFromDataSet(editLayer.data);
240 }
241 }
242
243 /**
244 * Constructs a new {@code ChangesetDialog}.
245 */
246 public ChangesetDialog() {
247 super(
248 tr("Changesets"),
249 "changesetdialog",
250 tr("Open the list of changesets in the current layer."),
251 null, /* no keyboard shortcut */
252 200, /* the preferred height */
253 false /* don't show if there is no preference */
254 );
255 build();
256 initWithCurrentData();
257 }
258
259 class DblClickHandler extends MouseAdapter {
260 @Override
261 public void mouseClicked(MouseEvent e) {
262 if (!SwingUtilities.isLeftMouseButton(e) || e.getClickCount() < 2)
263 return;
264 Set<Integer> sel = getCurrentChangesetListModel().getSelectedChangesetIds();
265 if (sel.isEmpty())
266 return;
267 if (Main.getLayerManager().getEditDataSet() == null)
268 return;
269 new SelectObjectsAction().selectObjectsByChangesetIds(Main.getLayerManager().getEditDataSet(), sel);
270 }
271
272 }
273
274 class FilterChangeHandler implements ItemListener {
275 @Override
276 public void itemStateChanged(ItemEvent e) {
277 Main.pref.put("changeset-dialog.for-selected-objects-only", cbInSelectionOnly.isSelected());
278 pnlList.removeAll();
279 if (cbInSelectionOnly.isSelected()) {
280 pnlList.add(new JScrollPane(lstInSelection), BorderLayout.CENTER);
281 } else {
282 pnlList.add(new JScrollPane(lstInActiveDataLayer), BorderLayout.CENTER);
283 }
284 validate();
285 repaint();
286 }
287 }
288
289 /**
290 * Selects objects for the currently selected changesets.
291 */
292 class SelectObjectsAction extends AbstractAction implements ListSelectionListener, ItemListener {
293
294 SelectObjectsAction() {
295 putValue(NAME, tr("Select"));
296 putValue(SHORT_DESCRIPTION, tr("Select all objects assigned to the currently selected changesets"));
297 new ImageProvider("dialogs", "select").getResource().attachImageIcon(this, true);
298 updateEnabledState();
299 }
300
301 public void selectObjectsByChangesetIds(DataSet ds, Set<Integer> ids) {
302 if (ds == null || ids == null)
303 return;
304 Set<OsmPrimitive> sel = new HashSet<>();
305 for (OsmPrimitive p: ds.allPrimitives()) {
306 if (ids.contains(p.getChangesetId())) {
307 sel.add(p);
308 }
309 }
310 ds.setSelected(sel);
311 }
312
313 @Override
314 public void actionPerformed(ActionEvent e) {
315 if (Main.getLayerManager().getEditLayer() == null)
316 return;
317 ChangesetListModel model = getCurrentChangesetListModel();
318 Set<Integer> sel = model.getSelectedChangesetIds();
319 if (sel.isEmpty())
320 return;
321
322 DataSet ds = Main.getLayerManager().getEditLayer().data;
323 selectObjectsByChangesetIds(ds, sel);
324 }
325
326 protected void updateEnabledState() {
327 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0);
328 }
329
330 @Override
331 public void itemStateChanged(ItemEvent e) {
332 updateEnabledState();
333
334 }
335
336 @Override
337 public void valueChanged(ListSelectionEvent e) {
338 updateEnabledState();
339 }
340 }
341
342 /**
343 * Downloads selected changesets
344 *
345 */
346 class ReadChangesetsAction extends AbstractAction implements ListSelectionListener, ItemListener {
347 ReadChangesetsAction() {
348 putValue(NAME, tr("Download"));
349 putValue(SHORT_DESCRIPTION, tr("Download information about the selected changesets from the OSM server"));
350 new ImageProvider("download").getResource().attachImageIcon(this, true);
351 updateEnabledState();
352 }
353
354 @Override
355 public void actionPerformed(ActionEvent e) {
356 ChangesetListModel model = getCurrentChangesetListModel();
357 Set<Integer> sel = model.getSelectedChangesetIds();
358 if (sel.isEmpty())
359 return;
360 ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(sel);
361 MainApplication.worker.submit(new PostDownloadHandler(task, task.download()));
362 }
363
364 protected void updateEnabledState() {
365 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0 && !Main.isOffline(OnlineResource.OSM_API));
366 }
367
368 @Override
369 public void itemStateChanged(ItemEvent e) {
370 updateEnabledState();
371 }
372
373 @Override
374 public void valueChanged(ListSelectionEvent e) {
375 updateEnabledState();
376 }
377 }
378
379 /**
380 * Closes the currently selected changesets
381 *
382 */
383 class CloseOpenChangesetsAction extends AbstractAction implements ListSelectionListener, ItemListener {
384 CloseOpenChangesetsAction() {
385 putValue(NAME, tr("Close open changesets"));
386 putValue(SHORT_DESCRIPTION, tr("Closes the selected open changesets"));
387 new ImageProvider("closechangeset").getResource().attachImageIcon(this, true);
388 updateEnabledState();
389 }
390
391 @Override
392 public void actionPerformed(ActionEvent e) {
393 List<Changeset> sel = getCurrentChangesetListModel().getSelectedOpenChangesets();
394 if (sel.isEmpty())
395 return;
396 MainApplication.worker.submit(new CloseChangesetTask(sel));
397 }
398
399 protected void updateEnabledState() {
400 setEnabled(getCurrentChangesetListModel().hasSelectedOpenChangesets());
401 }
402
403 @Override
404 public void itemStateChanged(ItemEvent e) {
405 updateEnabledState();
406 }
407
408 @Override
409 public void valueChanged(ListSelectionEvent e) {
410 updateEnabledState();
411 }
412 }
413
414 /**
415 * Show information about the currently selected changesets
416 *
417 */
418 class ShowChangesetInfoAction extends AbstractAction implements ListSelectionListener, ItemListener {
419 ShowChangesetInfoAction() {
420 putValue(NAME, tr("Show info"));
421 putValue(SHORT_DESCRIPTION, tr("Open a web page for each selected changeset"));
422 new ImageProvider("help/internet").getResource().attachImageIcon(this, true);
423 updateEnabledState();
424 }
425
426 @Override
427 public void actionPerformed(ActionEvent e) {
428 Set<Changeset> sel = getCurrentChangesetListModel().getSelectedChangesets();
429 if (sel.isEmpty())
430 return;
431 if (sel.size() > 10 && !AbstractInfoAction.confirmLaunchMultiple(sel.size()))
432 return;
433 String baseUrl = Main.getBaseBrowseUrl();
434 for (Changeset cs: sel) {
435 OpenBrowser.displayUrl(baseUrl + "/changeset/" + cs.getId());
436 }
437 }
438
439 protected void updateEnabledState() {
440 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0);
441 }
442
443 @Override
444 public void itemStateChanged(ItemEvent e) {
445 updateEnabledState();
446 }
447
448 @Override
449 public void valueChanged(ListSelectionEvent e) {
450 updateEnabledState();
451 }
452 }
453
454 /**
455 * Show information about the currently selected changesets
456 *
457 */
458 class LaunchChangesetManagerAction extends AbstractAction {
459 LaunchChangesetManagerAction() {
460 putValue(NAME, tr("Details"));
461 putValue(SHORT_DESCRIPTION, tr("Opens the Changeset Manager window for the selected changesets"));
462 new ImageProvider("dialogs/changeset", "changesetmanager").getResource().attachImageIcon(this, true);
463 }
464
465 @Override
466 public void actionPerformed(ActionEvent e) {
467 ChangesetListModel model = getCurrentChangesetListModel();
468 Set<Integer> sel = model.getSelectedChangesetIds();
469 LaunchChangesetManager.displayChangesets(sel);
470 }
471 }
472
473 /**
474 * A utility class to fetch changesets and display the changeset dialog.
475 */
476 public static final class LaunchChangesetManager {
477
478 private LaunchChangesetManager() {
479 // Hide implicit public constructor for utility classes
480 }
481
482 private static void launchChangesetManager(Collection<Integer> toSelect) {
483 ChangesetCacheManager cm = ChangesetCacheManager.getInstance();
484 if (cm.isVisible()) {
485 cm.setExtendedState(Frame.NORMAL);
486 cm.toFront();
487 cm.requestFocus();
488 } else {
489 cm.setVisible(true);
490 cm.toFront();
491 cm.requestFocus();
492 }
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 (!Main.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.