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