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