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