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

Last change on this file since 8254 was 8254, checked in by simon04, 9 years ago

see #10789 - History dialog: add button to open changeset dialog

  • Property svn:eol-style set to native
File size: 21.6 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.data.osm.Changeset;
38import org.openstreetmap.josm.data.osm.ChangesetCache;
39import org.openstreetmap.josm.data.osm.DataSet;
40import org.openstreetmap.josm.data.osm.OsmPrimitive;
41import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
42import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
43import org.openstreetmap.josm.gui.MapView;
44import org.openstreetmap.josm.gui.SideButton;
45import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetCacheManager;
46import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetHeaderDownloadTask;
47import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetInSelectionListModel;
48import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListCellRenderer;
49import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListModel;
50import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetsInActiveDataLayerListModel;
51import org.openstreetmap.josm.gui.help.HelpUtil;
52import org.openstreetmap.josm.gui.io.CloseChangesetTask;
53import org.openstreetmap.josm.gui.layer.OsmDataLayer;
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.OnlineResource;
58import org.openstreetmap.josm.tools.BugReportExceptionHandler;
59import org.openstreetmap.josm.tools.ImageProvider;
60import org.openstreetmap.josm.tools.OpenBrowser;
61
62/**
63 * ChangesetDialog is a toggle dialog which displays the current list of changesets.
64 * It either displays
65 * <ul>
66 * <li>the list of changesets the currently selected objects are assigned to</li>
67 * <li>the list of changesets objects in the current data layer are assigend to</li>
68 * </ul>
69 *
70 * The dialog offers actions to download and to close changesets. It can also launch an external
71 * browser with information about a changeset. Furthermore, it can select all objects in
72 * the current data layer being assigned to a specific changeset.
73 *
74 */
75public class ChangesetDialog extends ToggleDialog{
76 private ChangesetInSelectionListModel inSelectionModel;
77 private ChangesetsInActiveDataLayerListModel inActiveDataLayerModel;
78 private JList<Changeset> lstInSelection;
79 private JList<Changeset> lstInActiveDataLayer;
80 private JCheckBox cbInSelectionOnly;
81 private JPanel pnlList;
82
83 // the actions
84 private SelectObjectsAction selectObjectsAction;
85 private ReadChangesetsAction readChangesetAction;
86 private ShowChangesetInfoAction showChangesetInfoAction;
87 private CloseOpenChangesetsAction closeChangesetAction;
88 private LaunchChangesetManagerAction launchChangesetManagerAction;
89
90 private ChangesetDialogPopup popupMenu;
91
92 protected void buildChangesetsLists() {
93 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
94 inSelectionModel = new ChangesetInSelectionListModel(selectionModel);
95
96 lstInSelection = new JList<>(inSelectionModel);
97 lstInSelection.setSelectionModel(selectionModel);
98 lstInSelection.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
99 lstInSelection.setCellRenderer(new ChangesetListCellRenderer());
100
101 selectionModel = new DefaultListSelectionModel();
102 inActiveDataLayerModel = new ChangesetsInActiveDataLayerListModel(selectionModel);
103 lstInActiveDataLayer = new JList<>(inActiveDataLayerModel);
104 lstInActiveDataLayer.setSelectionModel(selectionModel);
105 lstInActiveDataLayer.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
106 lstInActiveDataLayer.setCellRenderer(new ChangesetListCellRenderer());
107
108 DblClickHandler dblClickHandler = new DblClickHandler();
109 lstInSelection.addMouseListener(dblClickHandler);
110 lstInActiveDataLayer.addMouseListener(dblClickHandler);
111 }
112
113 protected void registerAsListener() {
114 // let the model for changesets in the current selection listen to various events
115 ChangesetCache.getInstance().addChangesetCacheListener(inSelectionModel);
116 MapView.addEditLayerChangeListener(inSelectionModel);
117 DataSet.addSelectionListener(inSelectionModel);
118
119 // let the model for changesets in the current layer listen to various
120 // events and bootstrap it's content
121 ChangesetCache.getInstance().addChangesetCacheListener(inActiveDataLayerModel);
122 MapView.addEditLayerChangeListener(inActiveDataLayerModel);
123 OsmDataLayer editLayer = Main.main.getEditLayer();
124 if (editLayer != null) {
125 editLayer.data.addDataSetListener(inActiveDataLayerModel);
126 inActiveDataLayerModel.initFromDataSet(editLayer.data);
127 inSelectionModel.initFromPrimitives(editLayer.data.getAllSelected());
128 }
129 }
130
131 protected void unregisterAsListener() {
132 // remove the list model for the current edit layer as listener
133 //
134 ChangesetCache.getInstance().removeChangesetCacheListener(inActiveDataLayerModel);
135 MapView.removeEditLayerChangeListener(inActiveDataLayerModel);
136 OsmDataLayer editLayer = Main.main.getEditLayer();
137 if (editLayer != null) {
138 editLayer.data.removeDataSetListener(inActiveDataLayerModel);
139 }
140
141 // remove the list model for the changesets in the current selection as
142 // listener
143 //
144 MapView.removeEditLayerChangeListener(inSelectionModel);
145 DataSet.removeSelectionListener(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 pnl.add(cbInSelectionOnly = new JCheckBox(tr("For selected objects only")));
164 cbInSelectionOnly.setToolTipText(tr("<html>Select to show changesets for the currently selected objects only.<br>"
165 + "Unselect to show all changesets for objects in the current data layer.</html>"));
166 cbInSelectionOnly.setSelected(Main.pref.getBoolean("changeset-dialog.for-selected-objects-only", false));
167 return pnl;
168 }
169
170 protected JPanel buildListPanel() {
171 buildChangesetsLists();
172 JPanel pnl = new JPanel(new BorderLayout());
173 if (cbInSelectionOnly.isSelected()) {
174 pnl.add(new JScrollPane(lstInSelection));
175 } else {
176 pnl.add(new JScrollPane(lstInActiveDataLayer));
177 }
178 return pnl;
179 }
180
181 protected void build() {
182 JPanel pnl = new JPanel(new BorderLayout());
183 pnl.add(buildFilterPanel(), BorderLayout.NORTH);
184 pnl.add(pnlList = buildListPanel(), BorderLayout.CENTER);
185
186 cbInSelectionOnly.addItemListener(new FilterChangeHandler());
187
188 HelpUtil.setHelpContext(pnl, HelpUtil.ht("/Dialog/ChangesetList"));
189
190 // -- select objects action
191 selectObjectsAction = new SelectObjectsAction();
192 cbInSelectionOnly.addItemListener(selectObjectsAction);
193
194 // -- read changesets action
195 readChangesetAction = new ReadChangesetsAction();
196 cbInSelectionOnly.addItemListener(readChangesetAction);
197
198 // -- close changesets action
199 closeChangesetAction = new CloseOpenChangesetsAction();
200 cbInSelectionOnly.addItemListener(closeChangesetAction);
201
202 // -- show info action
203 showChangesetInfoAction = new ShowChangesetInfoAction();
204 cbInSelectionOnly.addItemListener(showChangesetInfoAction);
205
206 // -- launch changeset manager action
207 launchChangesetManagerAction = new LaunchChangesetManagerAction();
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(new SideButton[] {
216 new SideButton(selectObjectsAction, false),
217 new SideButton(readChangesetAction, false),
218 new SideButton(closeChangesetAction, false),
219 new SideButton(showChangesetInfoAction, false),
220 new SideButton(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 = Main.main.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 (Main.main.getCurrentDataSet() == null)
269 return;
270 new SelectObjectsAction().selectObjectsByChangesetIds(Main.main.getCurrentDataSet(), sel);
271 }
272
273 }
274
275 class FilterChangeHandler implements ItemListener {
276 @Override
277 public void itemStateChanged(ItemEvent e) {
278 Main.pref.put("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 public SelectObjectsAction() {
296 putValue(NAME, tr("Select"));
297 putValue(SHORT_DESCRIPTION, tr("Select all objects assigned to the currently selected changesets"));
298 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
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 (!Main.main.hasEditLayer())
317 return;
318 ChangesetListModel model = getCurrentChangesetListModel();
319 Set<Integer> sel = model.getSelectedChangesetIds();
320 if (sel.isEmpty())
321 return;
322
323 DataSet ds = Main.main.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 arg0) {
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 public ReadChangesetsAction() {
349 putValue(NAME, tr("Download"));
350 putValue(SHORT_DESCRIPTION, tr("Download information about the selected changesets from the OSM server"));
351 putValue(SMALL_ICON, ImageProvider.get("download"));
352 updateEnabledState();
353 }
354
355 @Override
356 public void actionPerformed(ActionEvent arg0) {
357 ChangesetListModel model = getCurrentChangesetListModel();
358 Set<Integer> sel = model.getSelectedChangesetIds();
359 if (sel.isEmpty())
360 return;
361 ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(sel);
362 Main.worker.submit(task);
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 arg0) {
371 updateEnabledState();
372
373 }
374
375 @Override
376 public void valueChanged(ListSelectionEvent e) {
377 updateEnabledState();
378 }
379 }
380
381 /**
382 * Closes the currently selected changesets
383 *
384 */
385 class CloseOpenChangesetsAction extends AbstractAction implements ListSelectionListener, ItemListener {
386 public CloseOpenChangesetsAction() {
387 putValue(NAME, tr("Close open changesets"));
388 putValue(SHORT_DESCRIPTION, tr("Closes the selected open changesets"));
389 putValue(SMALL_ICON, ImageProvider.get("closechangeset"));
390 updateEnabledState();
391 }
392
393 @Override
394 public void actionPerformed(ActionEvent arg0) {
395 List<Changeset> sel = getCurrentChangesetListModel().getSelectedOpenChangesets();
396 if (sel.isEmpty())
397 return;
398 Main.worker.submit(new CloseChangesetTask(sel));
399 }
400
401 protected void updateEnabledState() {
402 setEnabled(getCurrentChangesetListModel().hasSelectedOpenChangesets());
403 }
404
405 @Override
406 public void itemStateChanged(ItemEvent arg0) {
407 updateEnabledState();
408 }
409
410 @Override
411 public void valueChanged(ListSelectionEvent e) {
412 updateEnabledState();
413 }
414 }
415
416 /**
417 * Show information about the currently selected changesets
418 *
419 */
420 class ShowChangesetInfoAction extends AbstractAction implements ListSelectionListener, ItemListener {
421 public ShowChangesetInfoAction() {
422 putValue(NAME, tr("Show info"));
423 putValue(SHORT_DESCRIPTION, tr("Open a web page for each selected changeset"));
424 putValue(SMALL_ICON, ImageProvider.get("help/internet"));
425 updateEnabledState();
426 }
427
428 @Override
429 public void actionPerformed(ActionEvent arg0) {
430 Set<Changeset> sel = getCurrentChangesetListModel().getSelectedChangesets();
431 if (sel.isEmpty())
432 return;
433 if (sel.size() > 10 && ! AbstractInfoAction.confirmLaunchMultiple(sel.size()))
434 return;
435 String baseUrl = Main.getBaseBrowseUrl();
436 for (Changeset cs: sel) {
437 String url = baseUrl + "/changeset/" + cs.getId();
438 OpenBrowser.displayUrl(
439 url
440 );
441 }
442 }
443
444 protected void updateEnabledState() {
445 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0);
446 }
447
448 @Override
449 public void itemStateChanged(ItemEvent arg0) {
450 updateEnabledState();
451 }
452
453 @Override
454 public void valueChanged(ListSelectionEvent e) {
455 updateEnabledState();
456 }
457 }
458
459 /**
460 * Show information about the currently selected changesets
461 *
462 */
463 class LaunchChangesetManagerAction extends AbstractAction {
464 public LaunchChangesetManagerAction() {
465 putValue(NAME, tr("Details"));
466 putValue(SHORT_DESCRIPTION, tr("Opens the Changeset Manager window for the selected changesets"));
467 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "changesetmanager"));
468 }
469
470 @Override
471 public void actionPerformed(ActionEvent arg0) {
472 ChangesetListModel model = getCurrentChangesetListModel();
473 Set<Integer> sel = model.getSelectedChangesetIds();
474 LaunchChangesetManager.displayChangesets(sel);
475 }
476 }
477
478 /**
479 * A utility class to fetch changesets and display the changeset dialog.
480 */
481 public static class LaunchChangesetManager {
482
483 protected 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 = Main.worker.submit(task);
520 }
521
522 Runnable r = new Runnable() {
523 @Override
524 public void run() {
525 // first, wait for the download task to finish, if a download task was launched
526 if (future != null) {
527 try {
528 future.get();
529 } catch(InterruptedException e) {
530 Main.warn("InterruptedException in "+getClass().getSimpleName()+" while downloading changeset header");
531 } catch(ExecutionException e) {
532 Main.error(e);
533 BugReportExceptionHandler.handleException(e.getCause());
534 return;
535 }
536 }
537 if (task != null) {
538 if (task.isCanceled())
539 // don't launch the changeset manager if the download task was canceled
540 return;
541 if (task.isFailed()) {
542 toDownload.clear();
543 }
544 }
545 // launch the task
546 GuiHelper.runInEDT(new Runnable() {
547 @Override
548 public void run() {
549 launchChangesetManager(sel);
550 }
551 });
552 }
553 };
554 Main.worker.submit(r);
555 }
556 }
557
558 class ChangesetDialogPopup extends ListPopupMenu {
559 public ChangesetDialogPopup(JList<?> ... lists) {
560 super(lists);
561 add(selectObjectsAction);
562 addSeparator();
563 add(readChangesetAction);
564 add(closeChangesetAction);
565 addSeparator();
566 add(showChangesetInfoAction);
567 }
568 }
569
570 public void addPopupMenuSeparator() {
571 popupMenu.addSeparator();
572 }
573
574 public JMenuItem addPopupMenuAction(Action a) {
575 return popupMenu.add(a);
576 }
577}
Note: See TracBrowser for help on using the repository browser.