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