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

Last change on this file was 18338, checked in by Don-vip, 5 years ago

fix #21655 - fix selection of changesets after sorting the table

  • Property svn:eol-style set to native
File size: 28.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.changeset;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Component;
8import java.awt.Container;
9import java.awt.Dimension;
10import java.awt.FlowLayout;
11import java.awt.GraphicsEnvironment;
12import java.awt.Window;
13import java.awt.datatransfer.Clipboard;
14import java.awt.event.ActionEvent;
15import java.awt.event.KeyEvent;
16import java.awt.event.MouseEvent;
17import java.awt.event.WindowAdapter;
18import java.awt.event.WindowEvent;
19import java.util.Collection;
20import java.util.List;
21import java.util.Objects;
22import java.util.Set;
23import java.util.stream.Collectors;
24
25import javax.swing.AbstractAction;
26import javax.swing.DefaultListSelectionModel;
27import javax.swing.JButton;
28import javax.swing.JComponent;
29import javax.swing.JFrame;
30import javax.swing.JOptionPane;
31import javax.swing.JPanel;
32import javax.swing.JPopupMenu;
33import javax.swing.JScrollPane;
34import javax.swing.JSplitPane;
35import javax.swing.JTabbedPane;
36import javax.swing.JTable;
37import javax.swing.JToolBar;
38import javax.swing.KeyStroke;
39import javax.swing.ListSelectionModel;
40import javax.swing.TransferHandler;
41import javax.swing.event.ListSelectionEvent;
42import javax.swing.event.ListSelectionListener;
43
44import org.openstreetmap.josm.actions.downloadtasks.AbstractChangesetDownloadTask;
45import org.openstreetmap.josm.actions.downloadtasks.ChangesetContentDownloadTask;
46import org.openstreetmap.josm.actions.downloadtasks.ChangesetHeaderDownloadTask;
47import org.openstreetmap.josm.actions.downloadtasks.ChangesetQueryTask;
48import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
49import org.openstreetmap.josm.data.UserIdentityManager;
50import org.openstreetmap.josm.data.osm.Changeset;
51import org.openstreetmap.josm.data.osm.ChangesetCache;
52import org.openstreetmap.josm.data.osm.PrimitiveId;
53import org.openstreetmap.josm.gui.HelpAwareOptionPane;
54import org.openstreetmap.josm.gui.MainApplication;
55import org.openstreetmap.josm.gui.datatransfer.ChangesetTransferable;
56import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
57import org.openstreetmap.josm.gui.dialogs.changeset.query.ChangesetQueryDialog;
58import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
59import org.openstreetmap.josm.gui.help.HelpUtil;
60import org.openstreetmap.josm.gui.io.CloseChangesetTask;
61import org.openstreetmap.josm.gui.io.DownloadPrimitivesWithReferrersTask;
62import org.openstreetmap.josm.gui.util.GuiHelper;
63import org.openstreetmap.josm.gui.util.WindowGeometry;
64import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
65import org.openstreetmap.josm.io.ChangesetQuery;
66import org.openstreetmap.josm.io.NetworkManager;
67import org.openstreetmap.josm.io.OnlineResource;
68import org.openstreetmap.josm.tools.ImageProvider;
69import org.openstreetmap.josm.tools.InputMapUtils;
70import org.openstreetmap.josm.tools.Logging;
71
72/**
73 * ChangesetCacheManager manages the local cache of changesets
74 * retrieved from the OSM API. It displays both a table of the locally cached changesets
75 * and detail information about an individual changeset. It also provides actions for
76 * downloading, querying, closing changesets, in addition to removing changesets from
77 * the local cache.
78 * @since 2689
79 */
80public class ChangesetCacheManager extends JFrame {
81
82 /** the unique instance of the cache manager */
83 private static volatile ChangesetCacheManager instance;
84 private JTabbedPane pnlChangesetDetailTabs;
85
86 /**
87 * Replies the unique instance of the changeset cache manager
88 *
89 * @return the unique instance of the changeset cache manager
90 */
91 public static ChangesetCacheManager getInstance() {
92 if (instance == null) {
93 instance = new ChangesetCacheManager();
94 }
95 return instance;
96 }
97
98 /**
99 * Hides and destroys the unique instance of the changeset cache manager.
100 *
101 */
102 public static void destroyInstance() {
103 if (instance != null) {
104 instance.setVisible(false);
105 GuiHelper.destroyComponents(instance, false);
106 instance.dispose();
107 instance = null;
108 }
109 }
110
111 private ChangesetCacheManagerModel model;
112 private ChangesetCacheTableRowSorter sorter;
113 private JSplitPane spContent;
114 private boolean needsSplitPaneAdjustment;
115
116 private RemoveFromCacheAction actRemoveFromCacheAction;
117 private CloseSelectedChangesetsAction actCloseSelectedChangesetsAction;
118 private DownloadSelectedChangesetsAction actDownloadSelectedChangesets;
119 private DownloadSelectedChangesetContentAction actDownloadSelectedContent;
120 private DownloadSelectedChangesetObjectsAction actDownloadSelectedChangesetObjects;
121 private JTable tblChangesets;
122
123 /**
124 * Creates the various models required.
125 * @return the changeset cache model
126 */
127 static ChangesetCacheManagerModel buildModel() {
128 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
129 selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
130 return new ChangesetCacheManagerModel(selectionModel);
131 }
132
133 /**
134 * builds the toolbar panel in the heading of the dialog
135 *
136 * @return the toolbar panel
137 */
138 static JPanel buildToolbarPanel() {
139 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
140
141 JButton btn = new JButton(new QueryAction());
142 pnl.add(btn);
143 pnl.add(new SingleChangesetDownloadPanel());
144 pnl.add(new JButton(new DownloadMyChangesets()));
145
146 return pnl;
147 }
148
149 /**
150 * builds the button panel in the footer of the dialog
151 *
152 * @return the button row pane
153 */
154 static JPanel buildButtonPanel() {
155 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
156
157 //-- cancel and close action
158 pnl.add(new JButton(new CancelAction()));
159
160 //-- help action
161 pnl.add(new JButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Dialog/ChangesetManager"))));
162
163 return pnl;
164 }
165
166 /**
167 * Builds the panel with the changeset details
168 *
169 * @return the panel with the changeset details
170 */
171 protected JPanel buildChangesetDetailPanel() {
172 JPanel pnl = new JPanel(new BorderLayout());
173 JTabbedPane tp = new JTabbedPane();
174 pnlChangesetDetailTabs = tp;
175
176 // -- add the details panel
177 ChangesetDetailPanel pnlChangesetDetail = new ChangesetDetailPanel();
178 tp.add(pnlChangesetDetail);
179 model.addPropertyChangeListener(pnlChangesetDetail);
180
181 // -- add the tags panel
182 ChangesetTagsPanel pnlChangesetTags = new ChangesetTagsPanel();
183 tp.add(pnlChangesetTags);
184 model.addPropertyChangeListener(pnlChangesetTags);
185
186 // -- add the panel for the changeset content
187 ChangesetContentPanel pnlChangesetContent = new ChangesetContentPanel();
188 tp.add(pnlChangesetContent);
189 model.addPropertyChangeListener(pnlChangesetContent);
190
191 // -- add the panel for the changeset discussion
192 ChangesetDiscussionPanel pnlChangesetDiscussion = new ChangesetDiscussionPanel();
193 tp.add(pnlChangesetDiscussion);
194 model.addPropertyChangeListener(pnlChangesetDiscussion);
195
196 tp.setTitleAt(0, tr("Properties"));
197 tp.setToolTipTextAt(0, tr("Display the basic properties of the changeset"));
198 tp.setTitleAt(1, tr("Tags"));
199 tp.setToolTipTextAt(1, tr("Display the tags of the changeset"));
200 tp.setTitleAt(2, tr("Content"));
201 tp.setToolTipTextAt(2, tr("Display the objects created, updated, and deleted by the changeset"));
202 tp.setTitleAt(3, tr("Discussion"));
203 tp.setToolTipTextAt(3, tr("Display the public discussion around this changeset"));
204
205 pnl.add(tp, BorderLayout.CENTER);
206 return pnl;
207 }
208
209 /**
210 * builds the content panel of the dialog
211 *
212 * @return the content panel
213 */
214 protected JPanel buildContentPanel() {
215 JPanel pnl = new JPanel(new BorderLayout());
216
217 spContent = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
218 spContent.setLeftComponent(buildChangesetTablePanel());
219 spContent.setRightComponent(buildChangesetDetailPanel());
220 spContent.setOneTouchExpandable(true);
221 spContent.setDividerLocation(0.5);
222
223 pnl.add(spContent, BorderLayout.CENTER);
224 return pnl;
225 }
226
227 /**
228 * Builds the table with actions which can be applied to the currently visible changesets
229 * in the changeset table.
230 *
231 * @return changeset actions panel
232 */
233 protected JPanel buildChangesetTableActionPanel() {
234 JPanel pnl = new JPanel(new BorderLayout());
235
236 JToolBar tb = new JToolBar(JToolBar.VERTICAL);
237 tb.setFloatable(false);
238
239 // -- remove from cache action
240 model.getSelectionModel().addListSelectionListener(actRemoveFromCacheAction);
241 tb.add(actRemoveFromCacheAction);
242
243 // -- close selected changesets action
244 model.getSelectionModel().addListSelectionListener(actCloseSelectedChangesetsAction);
245 tb.add(actCloseSelectedChangesetsAction);
246
247 // -- download selected changesets
248 model.getSelectionModel().addListSelectionListener(actDownloadSelectedChangesets);
249 tb.add(actDownloadSelectedChangesets);
250
251 // -- download the content of the selected changesets
252 model.getSelectionModel().addListSelectionListener(actDownloadSelectedContent);
253 tb.add(actDownloadSelectedContent);
254
255 // -- download the objects contained in the selected changesets from the OSM server
256 model.getSelectionModel().addListSelectionListener(actDownloadSelectedChangesetObjects);
257 tb.add(actDownloadSelectedChangesetObjects);
258
259 pnl.add(tb, BorderLayout.CENTER);
260 return pnl;
261 }
262
263 /**
264 * Builds the panel with the table of changesets
265 *
266 * @return the panel with the table of changesets
267 */
268 protected JPanel buildChangesetTablePanel() {
269 JPanel pnl = new JPanel(new BorderLayout());
270 tblChangesets = new JTable(
271 model,
272 new ChangesetCacheTableColumnModel(),
273 model.getSelectionModel()
274 );
275 tblChangesets.setRowSorter(sorter);
276 tblChangesets.addMouseListener(new MouseEventHandler());
277 InputMapUtils.addEnterAction(tblChangesets, new ShowDetailAction(model));
278 model.getSelectionModel().addListSelectionListener(new ChangesetDetailViewSynchronizer(model));
279
280 // activate DEL on the table
281 tblChangesets.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "removeFromCache");
282 tblChangesets.getActionMap().put("removeFromCache", actRemoveFromCacheAction);
283 tblChangesets.getTableHeader().setReorderingAllowed(false);
284
285 tblChangesets.setTransferHandler(new TransferHandler() {
286 @Override
287 public void exportToClipboard(JComponent comp, Clipboard clip, int action) {
288 List<Changeset> changesets = model.getSelectedChangesets();
289 ChangesetTransferable transferable = new ChangesetTransferable(changesets);
290 ClipboardUtils.copy(transferable);
291 }
292 });
293
294 pnl.add(new JScrollPane(tblChangesets), BorderLayout.CENTER);
295 pnl.add(buildChangesetTableActionPanel(), BorderLayout.WEST);
296 return pnl;
297 }
298
299 protected void build() {
300 setTitle(tr("Changeset Manager"));
301 setIconImage(ImageProvider.get("dialogs/changeset", "changesetmanager").getImage());
302 Container cp = getContentPane();
303
304 cp.setLayout(new BorderLayout());
305
306 model = buildModel();
307 sorter = new ChangesetCacheTableRowSorter(model);
308 model.setChangesetCacheTableRowSorter(sorter);
309 actRemoveFromCacheAction = new RemoveFromCacheAction(model);
310 actCloseSelectedChangesetsAction = new CloseSelectedChangesetsAction(model);
311 actDownloadSelectedChangesets = new DownloadSelectedChangesetsAction(model);
312 actDownloadSelectedContent = new DownloadSelectedChangesetContentAction(model);
313 actDownloadSelectedChangesetObjects = new DownloadSelectedChangesetObjectsAction();
314
315 cp.add(buildToolbarPanel(), BorderLayout.NORTH);
316 cp.add(buildContentPanel(), BorderLayout.CENTER);
317 cp.add(buildButtonPanel(), BorderLayout.SOUTH);
318
319 // the help context
320 HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/ChangesetManager"));
321
322 // make the dialog respond to ESC
323 InputMapUtils.addEscapeAction(getRootPane(), new CancelAction());
324
325 // install a window event handler
326 addWindowListener(new WindowEventHandler());
327 }
328
329 /**
330 * Constructs a new {@code ChangesetCacheManager}.
331 */
332 public ChangesetCacheManager() {
333 build();
334 }
335
336 @Override
337 public void setVisible(boolean visible) {
338 if (visible) {
339 new WindowGeometry(
340 getClass().getName() + ".geometry",
341 WindowGeometry.centerInWindow(
342 getParent(),
343 new Dimension(1000, 600)
344 )
345 ).applySafe(this);
346 needsSplitPaneAdjustment = true;
347 model.init();
348
349 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
350 model.tearDown();
351 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
352 }
353 super.setVisible(visible);
354 }
355
356 /**
357 * Handler for window events
358 *
359 */
360 class WindowEventHandler extends WindowAdapter {
361 @Override
362 public void windowClosing(WindowEvent e) {
363 destroyInstance();
364 }
365
366 @Override
367 public void windowActivated(WindowEvent e) {
368 if (needsSplitPaneAdjustment) {
369 spContent.setDividerLocation(0.5);
370 needsSplitPaneAdjustment = false;
371 }
372 }
373 }
374
375 /**
376 * the cancel / close action
377 */
378 static class CancelAction extends AbstractAction {
379 CancelAction() {
380 putValue(NAME, tr("Close"));
381 new ImageProvider("cancel").getResource().attachImageIcon(this);
382 putValue(SHORT_DESCRIPTION, tr("Close the dialog"));
383 }
384
385 public void cancelAndClose() {
386 destroyInstance();
387 }
388
389 @Override
390 public void actionPerformed(ActionEvent e) {
391 cancelAndClose();
392 }
393 }
394
395 /**
396 * The action to query and download changesets
397 */
398 static class QueryAction extends AbstractAction {
399
400 QueryAction() {
401 putValue(NAME, tr("Query"));
402 new ImageProvider("dialogs", "search").getResource().attachImageIcon(this);
403 putValue(SHORT_DESCRIPTION, tr("Launch the dialog for querying changesets"));
404 setEnabled(!NetworkManager.isOffline(OnlineResource.OSM_API));
405 }
406
407 @Override
408 public void actionPerformed(ActionEvent evt) {
409 Window parent = GuiHelper.getWindowAncestorFor(evt);
410 ChangesetQueryDialog dialog = new ChangesetQueryDialog(parent);
411 dialog.initForUserInput();
412 dialog.setVisible(true);
413 if (dialog.isCanceled())
414 return;
415
416 try {
417 ChangesetQuery query = dialog.getChangesetQuery();
418 if (query != null) {
419 ChangesetCacheManager.getInstance().runDownloadTask(new ChangesetQueryTask(parent, query));
420 }
421 } catch (IllegalStateException e) {
422 Logging.error(e);
423 JOptionPane.showMessageDialog(parent, e.getMessage(), tr("Error"), JOptionPane.ERROR_MESSAGE);
424 }
425 }
426 }
427
428 /**
429 * Removes the selected changesets from the local changeset cache
430 *
431 */
432 static class RemoveFromCacheAction extends AbstractAction implements ListSelectionListener {
433 private final ChangesetCacheManagerModel model;
434
435 RemoveFromCacheAction(ChangesetCacheManagerModel model) {
436 putValue(NAME, tr("Remove from cache"));
437 new ImageProvider("dialogs", "delete").getResource().attachImageIcon(this);
438 putValue(SHORT_DESCRIPTION, tr("Remove the selected changesets from the local cache"));
439 this.model = model;
440 updateEnabledState();
441 }
442
443 @Override
444 public void actionPerformed(ActionEvent e) {
445 ChangesetCache.getInstance().remove(model.getSelectedChangesets());
446 }
447
448 protected void updateEnabledState() {
449 setEnabled(model.hasSelectedChangesets());
450 }
451
452 @Override
453 public void valueChanged(ListSelectionEvent e) {
454 if (e == null || !e.getValueIsAdjusting())
455 updateEnabledState();
456 }
457 }
458
459 /**
460 * Closes the selected changesets
461 *
462 */
463 static class CloseSelectedChangesetsAction extends AbstractAction implements ListSelectionListener {
464 private final ChangesetCacheManagerModel model;
465
466 CloseSelectedChangesetsAction(ChangesetCacheManagerModel model) {
467 putValue(NAME, tr("Close"));
468 new ImageProvider("closechangeset").getResource().attachImageIcon(this);
469 putValue(SHORT_DESCRIPTION, tr("Close the selected changesets"));
470 this.model = model;
471 updateEnabledState();
472 }
473
474 @Override
475 public void actionPerformed(ActionEvent e) {
476 MainApplication.worker.submit(new CloseChangesetTask(model.getSelectedChangesets()));
477 }
478
479 protected void updateEnabledState() {
480 List<Changeset> selected = model.getSelectedChangesets();
481 UserIdentityManager im = UserIdentityManager.getInstance();
482 for (Changeset cs: selected) {
483 if (cs.isOpen()) {
484 if (im.isPartiallyIdentified() && cs.getUser() != null && cs.getUser().getName().equals(im.getUserName())) {
485 setEnabled(true);
486 return;
487 }
488 if (im.isFullyIdentified() && cs.getUser() != null && cs.getUser().getId() == im.getUserId()) {
489 setEnabled(true);
490 return;
491 }
492 }
493 }
494 setEnabled(false);
495 }
496
497 @Override
498 public void valueChanged(ListSelectionEvent e) {
499 if (e == null || !e.getValueIsAdjusting())
500 updateEnabledState();
501 }
502 }
503
504 /**
505 * Downloads the selected changesets
506 *
507 */
508 static class DownloadSelectedChangesetsAction extends AbstractAction implements ListSelectionListener {
509 private final ChangesetCacheManagerModel model;
510
511 DownloadSelectedChangesetsAction(ChangesetCacheManagerModel model) {
512 putValue(NAME, tr("Update changeset"));
513 new ImageProvider("dialogs/changeset", "updatechangeset").getResource().attachImageIcon(this);
514 putValue(SHORT_DESCRIPTION, tr("Updates the selected changesets with current data from the OSM server"));
515 this.model = model;
516 updateEnabledState();
517 }
518
519 @Override
520 public void actionPerformed(ActionEvent e) {
521 if (!GraphicsEnvironment.isHeadless()) {
522 ChangesetCacheManager.getInstance().runDownloadTask(
523 ChangesetHeaderDownloadTask.buildTaskForChangesets(GuiHelper.getWindowAncestorFor(e), model.getSelectedChangesets()));
524 }
525 }
526
527 protected void updateEnabledState() {
528 setEnabled(model.hasSelectedChangesets() && !NetworkManager.isOffline(OnlineResource.OSM_API));
529 }
530
531 @Override
532 public void valueChanged(ListSelectionEvent e) {
533 if (e == null || !e.getValueIsAdjusting())
534 updateEnabledState();
535 }
536 }
537
538 /**
539 * Downloads the content of selected changesets from the OSM server
540 *
541 */
542 static class DownloadSelectedChangesetContentAction extends AbstractAction implements ListSelectionListener {
543 private final ChangesetCacheManagerModel model;
544
545 DownloadSelectedChangesetContentAction(ChangesetCacheManagerModel model) {
546 putValue(NAME, tr("Download changeset content"));
547 new ImageProvider("dialogs/changeset", "downloadchangesetcontent").getResource().attachImageIcon(this);
548 putValue(SHORT_DESCRIPTION, tr("Download the content of the selected changesets from the server"));
549 this.model = model;
550 updateEnabledState();
551 }
552
553 @Override
554 public void actionPerformed(ActionEvent e) {
555 if (!GraphicsEnvironment.isHeadless()) {
556 ChangesetCacheManager.getInstance().runDownloadTask(
557 new ChangesetContentDownloadTask(GuiHelper.getWindowAncestorFor(e), model.getSelectedChangesetIds()));
558 }
559 }
560
561 protected void updateEnabledState() {
562 setEnabled(model.hasSelectedChangesets() && !NetworkManager.isOffline(OnlineResource.OSM_API));
563 }
564
565 @Override
566 public void valueChanged(ListSelectionEvent e) {
567 if (e == null || !e.getValueIsAdjusting())
568 updateEnabledState();
569 }
570 }
571
572 /**
573 * Downloads the objects contained in the selected changesets from the OSM server
574 */
575 private class DownloadSelectedChangesetObjectsAction extends AbstractAction implements ListSelectionListener {
576
577 DownloadSelectedChangesetObjectsAction() {
578 putValue(NAME, tr("Download changed objects"));
579 new ImageProvider("downloadprimitive").getResource().attachImageIcon(this);
580 putValue(SHORT_DESCRIPTION, tr("Download the current version of the changed objects in the selected changesets"));
581 updateEnabledState();
582 }
583
584 @Override
585 public void actionPerformed(ActionEvent e) {
586 if (!GraphicsEnvironment.isHeadless()) {
587 if (model.getSelectedChangesets().stream().anyMatch(cs -> !cs.hasContent() || cs.isOpen()))
588 actDownloadSelectedContent.actionPerformed(e);
589 MainApplication.worker.submit(() -> {
590 final List<PrimitiveId> primitiveIds = model.getSelectedChangesets().stream()
591 .map(Changeset::getContent)
592 .filter(Objects::nonNull)
593 .flatMap(content -> content.getIds().stream())
594 .distinct()
595 .collect(Collectors.toList());
596 new DownloadPrimitivesWithReferrersTask(false, primitiveIds, true, true, null, null).run();
597 });
598 }
599 }
600
601 protected void updateEnabledState() {
602 setEnabled(model.hasSelectedChangesets() && !NetworkManager.isOffline(OnlineResource.OSM_API));
603 }
604
605 @Override
606 public void valueChanged(ListSelectionEvent e) {
607 if (e == null || !e.getValueIsAdjusting())
608 updateEnabledState();
609 }
610 }
611
612 static class ShowDetailAction extends AbstractAction {
613 private final ChangesetCacheManagerModel model;
614
615 ShowDetailAction(ChangesetCacheManagerModel model) {
616 this.model = model;
617 }
618
619 protected void showDetails() {
620 List<Changeset> selected = model.getSelectedChangesets();
621 if (selected.size() == 1) {
622 model.setChangesetInDetailView(selected.get(0));
623 }
624 }
625
626 @Override
627 public void actionPerformed(ActionEvent e) {
628 showDetails();
629 }
630 }
631
632 static class DownloadMyChangesets extends AbstractAction {
633 DownloadMyChangesets() {
634 putValue(NAME, tr("My changesets"));
635 new ImageProvider("dialogs/changeset", "downloadchangeset").getResource().attachImageIcon(this);
636 putValue(SHORT_DESCRIPTION, tr("Download my changesets from the OSM server (max. 100 changesets)"));
637 setEnabled(!NetworkManager.isOffline(OnlineResource.OSM_API));
638 }
639
640 protected void alertAnonymousUser(Component parent) {
641 HelpAwareOptionPane.showOptionDialog(
642 parent,
643 tr("<html>JOSM is currently running with an anonymous user. It cannot download<br>"
644 + "your changesets from the OSM server unless you enter your OSM user name<br>"
645 + "in the JOSM preferences.</html>"
646 ),
647 tr("Warning"),
648 JOptionPane.WARNING_MESSAGE,
649 HelpUtil.ht("/Dialog/ChangesetManager#CanDownloadMyChangesets")
650 );
651 }
652
653 @Override
654 public void actionPerformed(ActionEvent e) {
655 Window parent = GuiHelper.getWindowAncestorFor(e);
656 try {
657 ChangesetQuery query = ChangesetQuery.forCurrentUser();
658 if (!GraphicsEnvironment.isHeadless()) {
659 ChangesetCacheManager.getInstance().runDownloadTask(new ChangesetQueryTask(parent, query));
660 }
661 } catch (IllegalStateException ex) {
662 alertAnonymousUser(parent);
663 Logging.trace(ex);
664 }
665 }
666 }
667
668 class MouseEventHandler extends PopupMenuLauncher {
669
670 MouseEventHandler() {
671 super(new ChangesetTablePopupMenu());
672 }
673
674 @Override
675 public void mouseClicked(MouseEvent evt) {
676 if (isDoubleClick(evt)) {
677 new ShowDetailAction(model).showDetails();
678 }
679 }
680 }
681
682 class ChangesetTablePopupMenu extends JPopupMenu {
683 ChangesetTablePopupMenu() {
684 add(actRemoveFromCacheAction);
685 add(actCloseSelectedChangesetsAction);
686 add(actDownloadSelectedChangesets);
687 add(actDownloadSelectedContent);
688 add(actDownloadSelectedChangesetObjects);
689 }
690 }
691
692 static class ChangesetDetailViewSynchronizer implements ListSelectionListener {
693 private final ChangesetCacheManagerModel model;
694
695 ChangesetDetailViewSynchronizer(ChangesetCacheManagerModel model) {
696 this.model = model;
697 }
698
699 @Override
700 public void valueChanged(ListSelectionEvent e) {
701 if (e != null && e.getValueIsAdjusting())
702 return;
703
704 List<Changeset> selected = model.getSelectedChangesets();
705 if (selected.size() == 1) {
706 model.setChangesetInDetailView(selected.get(0));
707 } else {
708 model.setChangesetInDetailView(null);
709 }
710 }
711 }
712
713 /**
714 * Returns the changeset cache model.
715 * @return the changeset cache model
716 * @since 12495
717 */
718 public ChangesetCacheManagerModel getModel() {
719 return model;
720 }
721
722 /**
723 * Selects the changesets in <code>changesets</code>, provided the
724 * respective changesets are already present in the local changeset cache.
725 *
726 * @param changesets the collection of changesets. If {@code null}, the
727 * selection is cleared.
728 */
729 public void setSelectedChangesets(Collection<Changeset> changesets) {
730 model.setSelectedChangesets(changesets);
731 final int idx = model.getSelectionModel().getMinSelectionIndex();
732 if (idx < 0)
733 return;
734 GuiHelper.runInEDTAndWait(() -> tblChangesets.scrollRectToVisible(tblChangesets.getCellRect(idx, 0, true)));
735 repaint();
736 }
737
738 /**
739 * Selects the changesets with the ids in <code>ids</code>, provided the
740 * respective changesets are already present in the local changeset cache.
741 *
742 * @param ids the collection of ids. If null, the selection is cleared.
743 */
744 public void setSelectedChangesetsById(Collection<Integer> ids) {
745 if (ids == null) {
746 setSelectedChangesets(null);
747 return;
748 }
749 ChangesetCache cc = ChangesetCache.getInstance();
750 Set<Changeset> toSelect = ids.stream()
751 .filter(cc::contains)
752 .map(cc::get)
753 .collect(Collectors.toSet());
754 setSelectedChangesets(toSelect);
755 }
756
757 /**
758 * Selects the given component in the detail tabbed panel
759 * @param clazz the class of the component to select
760 */
761 public void setSelectedComponentInDetailPanel(Class<? extends JComponent> clazz) {
762 for (Component component : pnlChangesetDetailTabs.getComponents()) {
763 if (component.getClass().equals(clazz)) {
764 pnlChangesetDetailTabs.setSelectedComponent(component);
765 break;
766 }
767 }
768 }
769
770 /**
771 * Runs the given changeset download task.
772 * @param task The changeset download task to run
773 */
774 public void runDownloadTask(final AbstractChangesetDownloadTask task) {
775 MainApplication.worker.submit(new PostDownloadHandler(task, task.download()));
776 MainApplication.worker.submit(() -> {
777 if (task.isCanceled() || task.isFailed())
778 return;
779 GuiHelper.runInEDT(() -> setSelectedChangesets(task.getDownloadedData()));
780 });
781 }
782}
Note: See TracBrowser for help on using the repository browser.