001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.dialogs; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.BorderLayout; 007 import java.awt.FlowLayout; 008 import java.awt.Frame; 009 import java.awt.event.ActionEvent; 010 import java.awt.event.ItemEvent; 011 import java.awt.event.ItemListener; 012 import java.awt.event.MouseAdapter; 013 import java.awt.event.MouseEvent; 014 import java.util.Arrays; 015 import java.util.Collection; 016 import java.util.HashSet; 017 import java.util.List; 018 import java.util.Set; 019 import java.util.concurrent.ExecutionException; 020 import java.util.concurrent.Future; 021 022 import javax.swing.AbstractAction; 023 import javax.swing.Action; 024 import javax.swing.DefaultListSelectionModel; 025 import javax.swing.JCheckBox; 026 import javax.swing.JList; 027 import javax.swing.JMenuItem; 028 import javax.swing.JPanel; 029 import javax.swing.JScrollPane; 030 import javax.swing.ListSelectionModel; 031 import javax.swing.SwingUtilities; 032 import javax.swing.event.ListSelectionEvent; 033 import javax.swing.event.ListSelectionListener; 034 035 import org.openstreetmap.josm.Main; 036 import org.openstreetmap.josm.actions.AbstractInfoAction; 037 import org.openstreetmap.josm.data.osm.Changeset; 038 import org.openstreetmap.josm.data.osm.ChangesetCache; 039 import org.openstreetmap.josm.data.osm.DataSet; 040 import org.openstreetmap.josm.data.osm.OsmPrimitive; 041 import org.openstreetmap.josm.data.osm.event.DatasetEventManager; 042 import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; 043 import org.openstreetmap.josm.gui.MapFrame; 044 import org.openstreetmap.josm.gui.MapView; 045 import org.openstreetmap.josm.gui.SideButton; 046 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetCacheManager; 047 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetHeaderDownloadTask; 048 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetInSelectionListModel; 049 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListCellRenderer; 050 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListModel; 051 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetsInActiveDataLayerListModel; 052 import org.openstreetmap.josm.gui.help.HelpUtil; 053 import org.openstreetmap.josm.gui.io.CloseChangesetTask; 054 import org.openstreetmap.josm.gui.widgets.ListPopupMenu; 055 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 056 import org.openstreetmap.josm.tools.BugReportExceptionHandler; 057 import org.openstreetmap.josm.tools.ImageProvider; 058 import org.openstreetmap.josm.tools.OpenBrowser; 059 060 /** 061 * ChangesetDialog is a toggle dialog which displays the current list of changesets. 062 * It either displays 063 * <ul> 064 * <li>the list of changesets the currently selected objects are assigned to</li> 065 * <li>the list of changesets objects in the current data layer are assigend to</li> 066 * </ul> 067 * 068 * The dialog offers actions to download and to close changesets. It can also launch an external 069 * browser with information about a changeset. Furthermore, it can select all objects in 070 * the current data layer being assigned to a specific changeset. 071 * 072 */ 073 public class ChangesetDialog extends ToggleDialog{ 074 private ChangesetInSelectionListModel inSelectionModel; 075 private ChangesetsInActiveDataLayerListModel inActiveDataLayerModel; 076 private JList lstInSelection; 077 private JList lstInActiveDataLayer; 078 private JCheckBox cbInSelectionOnly; 079 private JPanel pnlList; 080 081 // the actions 082 private SelectObjectsAction selectObjectsAction; 083 private ReadChangesetsAction readChangesetAction; 084 private ShowChangesetInfoAction showChangesetInfoAction; 085 private CloseOpenChangesetsAction closeChangesetAction; 086 private LaunchChangesetManagerAction launchChangesetManagerAction; 087 088 private ChangesetDialogPopup popupMenu; 089 090 protected void buildChangesetsLists() { 091 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); 092 inSelectionModel = new ChangesetInSelectionListModel(selectionModel); 093 094 lstInSelection = new JList(inSelectionModel); 095 lstInSelection.setSelectionModel(selectionModel); 096 lstInSelection.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 097 lstInSelection.setCellRenderer(new ChangesetListCellRenderer()); 098 099 selectionModel = new DefaultListSelectionModel(); 100 inActiveDataLayerModel = new ChangesetsInActiveDataLayerListModel(selectionModel); 101 lstInActiveDataLayer = new JList(inActiveDataLayerModel); 102 lstInActiveDataLayer.setSelectionModel(selectionModel); 103 lstInActiveDataLayer.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 104 lstInActiveDataLayer.setCellRenderer(new ChangesetListCellRenderer()); 105 106 DblClickHandler dblClickHandler = new DblClickHandler(); 107 lstInSelection.addMouseListener(dblClickHandler); 108 lstInActiveDataLayer.addMouseListener(dblClickHandler); 109 110 ChangesetPopupMenuLauncher popupMenuLauncher = new ChangesetPopupMenuLauncher(); 111 lstInSelection.addMouseListener(popupMenuLauncher); 112 lstInActiveDataLayer.addMouseListener(popupMenuLauncher); 113 } 114 115 protected void registerAsListener() { 116 // let the model for changesets in the current selection listen to various 117 // events 118 ChangesetCache.getInstance().addChangesetCacheListener(inSelectionModel); 119 MapView.addEditLayerChangeListener(inSelectionModel); 120 DataSet.addSelectionListener(inSelectionModel); 121 122 // let the model for changesets in the current layer listen to various 123 // events and bootstrap it's content 124 ChangesetCache.getInstance().addChangesetCacheListener(inActiveDataLayerModel); 125 MapView.addEditLayerChangeListener(inActiveDataLayerModel); 126 if (Main.main.getEditLayer() != null) { 127 Main.main.getEditLayer().data.addDataSetListener(inActiveDataLayerModel); 128 inActiveDataLayerModel.initFromDataSet(Main.main.getEditLayer().data); 129 inSelectionModel.initFromPrimitives(Main.main.getEditLayer().data.getAllSelected()); 130 } 131 } 132 133 protected void unregisterAsListener() { 134 // remove the list model for the current edit layer as listener 135 // 136 ChangesetCache.getInstance().removeChangesetCacheListener(inActiveDataLayerModel); 137 MapView.removeEditLayerChangeListener(inActiveDataLayerModel); 138 if (Main.main.getEditLayer() != null) { 139 Main.main.getEditLayer().data.removeDataSetListener(inActiveDataLayerModel); 140 } 141 142 // remove the list model for the changesets in the current selection as 143 // listener 144 // 145 MapView.removeEditLayerChangeListener(inSelectionModel); 146 DataSet.removeSelectionListener(inSelectionModel); 147 } 148 149 @Override 150 public void showNotify() { 151 registerAsListener(); 152 DatasetEventManager.getInstance().addDatasetListener(inActiveDataLayerModel, FireMode.IN_EDT); 153 } 154 155 @Override 156 public void hideNotify() { 157 unregisterAsListener(); 158 DatasetEventManager.getInstance().removeDatasetListener(inActiveDataLayerModel); 159 } 160 161 protected JPanel buildFilterPanel() { 162 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); 163 pnl.setBorder(null); 164 pnl.add(cbInSelectionOnly = new JCheckBox(tr("For selected objects only"))); 165 cbInSelectionOnly.setToolTipText(tr("<html>Select to show changesets for the currently selected objects only.<br>" 166 + "Unselect to show all changesets for objects in the current data layer.</html>")); 167 cbInSelectionOnly.setSelected(Main.pref.getBoolean("changeset-dialog.for-selected-objects-only", false)); 168 return pnl; 169 } 170 171 protected JPanel buildListPanel() { 172 buildChangesetsLists(); 173 JPanel pnl = new JPanel(new BorderLayout()); 174 if (cbInSelectionOnly.isSelected()) { 175 pnl.add(new JScrollPane(lstInSelection)); 176 } else { 177 pnl.add(new JScrollPane(lstInActiveDataLayer)); 178 } 179 return pnl; 180 } 181 182 protected void build() { 183 JPanel pnl = new JPanel(new BorderLayout()); 184 pnl.add(buildFilterPanel(), BorderLayout.NORTH); 185 pnl.add(pnlList = buildListPanel(), BorderLayout.CENTER); 186 187 cbInSelectionOnly.addItemListener(new FilterChangeHandler()); 188 189 HelpUtil.setHelpContext(pnl, HelpUtil.ht("/Dialog/ChangesetListDialog")); 190 191 // -- select objects action 192 selectObjectsAction = new SelectObjectsAction(); 193 cbInSelectionOnly.addItemListener(selectObjectsAction); 194 195 // -- read changesets action 196 readChangesetAction = new ReadChangesetsAction(); 197 cbInSelectionOnly.addItemListener(readChangesetAction); 198 199 // -- close changesets action 200 closeChangesetAction = new CloseOpenChangesetsAction(); 201 cbInSelectionOnly.addItemListener(closeChangesetAction); 202 203 // -- show info action 204 showChangesetInfoAction = new ShowChangesetInfoAction(); 205 cbInSelectionOnly.addItemListener(showChangesetInfoAction); 206 207 // -- launch changeset manager action 208 launchChangesetManagerAction = new LaunchChangesetManagerAction(); 209 cbInSelectionOnly.addItemListener(launchChangesetManagerAction); 210 211 popupMenu = new ChangesetDialogPopup(lstInActiveDataLayer, lstInSelection); 212 213 createLayout(pnl, false, Arrays.asList(new SideButton[] { 214 new SideButton(selectObjectsAction, false), 215 new SideButton(readChangesetAction, false), 216 new SideButton(closeChangesetAction, false), 217 new SideButton(showChangesetInfoAction, false), 218 new SideButton(launchChangesetManagerAction, false) 219 })); 220 } 221 222 protected JList getCurrentChangesetList() { 223 if (cbInSelectionOnly.isSelected()) 224 return lstInSelection; 225 return lstInActiveDataLayer; 226 } 227 228 protected ChangesetListModel getCurrentChangesetListModel() { 229 if (cbInSelectionOnly.isSelected()) 230 return inSelectionModel; 231 return inActiveDataLayerModel; 232 } 233 234 protected void initWithCurrentData() { 235 if (Main.main.getEditLayer() != null) { 236 inSelectionModel.initFromPrimitives(Main.main.getEditLayer().data.getAllSelected()); 237 inActiveDataLayerModel.initFromDataSet(Main.main.getEditLayer().data); 238 } 239 } 240 241 public ChangesetDialog(MapFrame mapFrame) { 242 super( 243 tr("Changesets"), 244 "changesetdialog", 245 tr("Open the list of changesets in the current layer."), 246 null, /* no keyboard shortcut */ 247 200, /* the preferred height */ 248 false /* don't show if there is no preference */ 249 ); 250 build(); 251 initWithCurrentData(); 252 } 253 254 class DblClickHandler extends MouseAdapter { 255 @Override 256 public void mouseClicked(MouseEvent e) { 257 if (!SwingUtilities.isLeftMouseButton(e) || e.getClickCount() < 2) 258 return; 259 Set<Integer> sel = getCurrentChangesetListModel().getSelectedChangesetIds(); 260 if (sel.isEmpty()) 261 return; 262 if (Main.main.getCurrentDataSet() == null) 263 return; 264 new SelectObjectsAction().selectObjectsByChangesetIds(Main.main.getCurrentDataSet(), sel); 265 } 266 267 } 268 269 class FilterChangeHandler implements ItemListener { 270 public void itemStateChanged(ItemEvent e) { 271 Main.pref.put("changeset-dialog.for-selected-objects-only", cbInSelectionOnly.isSelected()); 272 pnlList.removeAll(); 273 if (cbInSelectionOnly.isSelected()) { 274 pnlList.add(new JScrollPane(lstInSelection), BorderLayout.CENTER); 275 } else { 276 pnlList.add(new JScrollPane(lstInActiveDataLayer), BorderLayout.CENTER); 277 } 278 validate(); 279 repaint(); 280 } 281 } 282 283 /** 284 * Selects objects for the currently selected changesets. 285 */ 286 class SelectObjectsAction extends AbstractAction implements ListSelectionListener, ItemListener{ 287 288 public SelectObjectsAction() { 289 putValue(NAME, tr("Select")); 290 putValue(SHORT_DESCRIPTION, tr("Select all objects assigned to the currently selected changesets")); 291 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select")); 292 updateEnabledState(); 293 } 294 295 public void selectObjectsByChangesetIds(DataSet ds, Set<Integer> ids) { 296 if (ds == null || ids == null) 297 return; 298 Set<OsmPrimitive> sel = new HashSet<OsmPrimitive>(); 299 for (OsmPrimitive p: ds.allPrimitives()) { 300 if (ids.contains(p.getChangesetId())) { 301 sel.add(p); 302 } 303 } 304 ds.setSelected(sel); 305 } 306 307 public void actionPerformed(ActionEvent e) { 308 if (Main.main.getEditLayer() == null) 309 return; 310 ChangesetListModel model = getCurrentChangesetListModel(); 311 Set<Integer> sel = model.getSelectedChangesetIds(); 312 if (sel.isEmpty()) 313 return; 314 315 DataSet ds = Main.main.getEditLayer().data; 316 selectObjectsByChangesetIds(ds,sel); 317 } 318 319 protected void updateEnabledState() { 320 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0); 321 } 322 323 public void itemStateChanged(ItemEvent arg0) { 324 updateEnabledState(); 325 326 } 327 328 public void valueChanged(ListSelectionEvent e) { 329 updateEnabledState(); 330 } 331 } 332 333 /** 334 * Downloads selected changesets 335 * 336 */ 337 class ReadChangesetsAction extends AbstractAction implements ListSelectionListener, ItemListener{ 338 public ReadChangesetsAction() { 339 putValue(NAME, tr("Download")); 340 putValue(SHORT_DESCRIPTION, tr("Download information about the selected changesets from the OSM server")); 341 putValue(SMALL_ICON, ImageProvider.get("download")); 342 updateEnabledState(); 343 } 344 345 public void actionPerformed(ActionEvent arg0) { 346 ChangesetListModel model = getCurrentChangesetListModel(); 347 Set<Integer> sel = model.getSelectedChangesetIds(); 348 if (sel.isEmpty()) 349 return; 350 ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(sel); 351 Main.worker.submit(task); 352 } 353 354 protected void updateEnabledState() { 355 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0); 356 } 357 358 public void itemStateChanged(ItemEvent arg0) { 359 updateEnabledState(); 360 361 } 362 363 public void valueChanged(ListSelectionEvent e) { 364 updateEnabledState(); 365 } 366 } 367 368 /** 369 * Closes the currently selected changesets 370 * 371 */ 372 class CloseOpenChangesetsAction extends AbstractAction implements ListSelectionListener, ItemListener { 373 public CloseOpenChangesetsAction() { 374 putValue(NAME, tr("Close open changesets")); 375 putValue(SHORT_DESCRIPTION, tr("Closes the selected open changesets")); 376 putValue(SMALL_ICON, ImageProvider.get("closechangeset")); 377 updateEnabledState(); 378 } 379 380 public void actionPerformed(ActionEvent arg0) { 381 List<Changeset> sel = getCurrentChangesetListModel().getSelectedOpenChangesets(); 382 if (sel.isEmpty()) 383 return; 384 Main.worker.submit(new CloseChangesetTask(sel)); 385 } 386 387 protected void updateEnabledState() { 388 setEnabled(getCurrentChangesetListModel().hasSelectedOpenChangesets()); 389 } 390 391 public void itemStateChanged(ItemEvent arg0) { 392 updateEnabledState(); 393 } 394 395 public void valueChanged(ListSelectionEvent e) { 396 updateEnabledState(); 397 } 398 } 399 400 /** 401 * Show information about the currently selected changesets 402 * 403 */ 404 class ShowChangesetInfoAction extends AbstractAction implements ListSelectionListener, ItemListener { 405 public ShowChangesetInfoAction() { 406 putValue(NAME, tr("Show info")); 407 putValue(SHORT_DESCRIPTION, tr("Open a web page for each selected changeset")); 408 putValue(SMALL_ICON, ImageProvider.get("about")); 409 updateEnabledState(); 410 } 411 412 public void actionPerformed(ActionEvent arg0) { 413 Set<Changeset> sel = getCurrentChangesetListModel().getSelectedChangesets(); 414 if (sel.isEmpty()) 415 return; 416 if (sel.size() > 10 && ! AbstractInfoAction.confirmLaunchMultiple(sel.size())) 417 return; 418 String baseUrl = AbstractInfoAction.getBaseBrowseUrl(); 419 for (Changeset cs: sel) { 420 String url = baseUrl + "/changeset/" + cs.getId(); 421 OpenBrowser.displayUrl( 422 url 423 ); 424 } 425 } 426 427 protected void updateEnabledState() { 428 setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0); 429 } 430 431 public void itemStateChanged(ItemEvent arg0) { 432 updateEnabledState(); 433 } 434 435 public void valueChanged(ListSelectionEvent e) { 436 updateEnabledState(); 437 } 438 } 439 440 /** 441 * Show information about the currently selected changesets 442 * 443 */ 444 class LaunchChangesetManagerAction extends AbstractAction implements ListSelectionListener, ItemListener { 445 public LaunchChangesetManagerAction() { 446 putValue(NAME, tr("Details")); 447 putValue(SHORT_DESCRIPTION, tr("Opens the Changeset Manager window for the selected changesets")); 448 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "changesetmanager")); 449 } 450 451 protected void launchChangesetManager(Collection<Integer> toSelect) { 452 ChangesetCacheManager cm = ChangesetCacheManager.getInstance(); 453 if (cm.isVisible()) { 454 cm.setExtendedState(Frame.NORMAL); 455 cm.toFront(); 456 cm.requestFocus(); 457 } else { 458 cm.setVisible(true); 459 cm.toFront(); 460 cm.requestFocus(); 461 } 462 cm.setSelectedChangesetsById(toSelect); 463 } 464 465 public void actionPerformed(ActionEvent arg0) { 466 ChangesetListModel model = getCurrentChangesetListModel(); 467 Set<Integer> sel = model.getSelectedChangesetIds(); 468 final Set<Integer> toDownload = new HashSet<Integer>(); 469 ChangesetCache cc = ChangesetCache.getInstance(); 470 for (int id: sel) { 471 if (!cc.contains(id)) { 472 toDownload.add(id); 473 } 474 } 475 476 final ChangesetHeaderDownloadTask task; 477 final Future<?> future; 478 if (toDownload.isEmpty()) { 479 task = null; 480 future = null; 481 } else { 482 task = new ChangesetHeaderDownloadTask(toDownload); 483 future = Main.worker.submit(task); 484 } 485 486 Runnable r = new Runnable() { 487 public void run() { 488 // first, wait for the download task to finish, if a download 489 // task was launched 490 if (future != null) { 491 try { 492 future.get(); 493 } catch(InterruptedException e) { 494 e.printStackTrace(); 495 } catch(ExecutionException e){ 496 e.printStackTrace(); 497 BugReportExceptionHandler.handleException(e.getCause()); 498 return; 499 } 500 } 501 if (task != null) { 502 if (task.isCanceled()) 503 // don't launch the changeset manager if the download task 504 // was canceled 505 return; 506 if (task.isFailed()) { 507 toDownload.clear(); 508 } 509 } 510 // launch the task 511 launchChangesetManager(toDownload); 512 } 513 }; 514 Main.worker.submit(r); 515 } 516 517 public void itemStateChanged(ItemEvent arg0) { 518 } 519 520 public void valueChanged(ListSelectionEvent e) { 521 } 522 } 523 524 class ChangesetPopupMenuLauncher extends PopupMenuLauncher { 525 @Override 526 public void launch(MouseEvent evt) { 527 JList lst = getCurrentChangesetList(); 528 if (lst.getSelectedIndices().length == 0) { 529 int idx = lst.locationToIndex(evt.getPoint()); 530 if (idx >=0) { 531 lst.getSelectionModel().addSelectionInterval(idx, idx); 532 } 533 } 534 popupMenu.show(lst, evt.getX(), evt.getY()); 535 } 536 } 537 538 class ChangesetDialogPopup extends ListPopupMenu { 539 public ChangesetDialogPopup(JList ... lists) { 540 super(lists); 541 add(selectObjectsAction); 542 addSeparator(); 543 add(readChangesetAction); 544 add(closeChangesetAction); 545 addSeparator(); 546 add(showChangesetInfoAction); 547 } 548 } 549 550 public void addPopupMenuSeparator() { 551 popupMenu.addSeparator(); 552 } 553 554 public JMenuItem addPopupMenuAction(Action a) { 555 return popupMenu.add(a); 556 } 557 }