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