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