001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.changeset; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trn; 006 007import java.awt.BorderLayout; 008import java.awt.FlowLayout; 009import java.awt.event.ActionEvent; 010import java.awt.event.ComponentAdapter; 011import java.awt.event.ComponentEvent; 012import java.beans.PropertyChangeEvent; 013import java.beans.PropertyChangeListener; 014import java.util.ArrayList; 015import java.util.Collection; 016import java.util.HashSet; 017import java.util.List; 018import java.util.Set; 019 020import javax.swing.AbstractAction; 021import javax.swing.BorderFactory; 022import javax.swing.DefaultListSelectionModel; 023import javax.swing.JButton; 024import javax.swing.JOptionPane; 025import javax.swing.JPanel; 026import javax.swing.JPopupMenu; 027import javax.swing.JScrollPane; 028import javax.swing.JSeparator; 029import javax.swing.JTable; 030import javax.swing.JToolBar; 031import javax.swing.event.ListSelectionEvent; 032import javax.swing.event.ListSelectionListener; 033 034import org.openstreetmap.josm.Main; 035import org.openstreetmap.josm.actions.AutoScaleAction; 036import org.openstreetmap.josm.actions.downloadtasks.ChangesetContentDownloadTask; 037import org.openstreetmap.josm.data.osm.Changeset; 038import org.openstreetmap.josm.data.osm.OsmPrimitive; 039import org.openstreetmap.josm.data.osm.PrimitiveId; 040import org.openstreetmap.josm.data.osm.history.History; 041import org.openstreetmap.josm.data.osm.history.HistoryDataSet; 042import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; 043import org.openstreetmap.josm.gui.HelpAwareOptionPane; 044import org.openstreetmap.josm.gui.help.HelpUtil; 045import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager; 046import org.openstreetmap.josm.gui.history.HistoryLoadTask; 047import org.openstreetmap.josm.gui.io.DownloadPrimitivesWithReferrersTask; 048import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 049import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 050import org.openstreetmap.josm.gui.layer.OsmDataLayer; 051import org.openstreetmap.josm.gui.util.GuiHelper; 052import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 053import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 054import org.openstreetmap.josm.tools.ImageProvider; 055import org.openstreetmap.josm.tools.Utils; 056import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler; 057 058/** 059 * The panel which displays the content of a changeset in a scrollable table. 060 * 061 * It listens to property change events for {@link ChangesetCacheManagerModel#CHANGESET_IN_DETAIL_VIEW_PROP} 062 * and updates its view accordingly. 063 * 064 */ 065public class ChangesetContentPanel extends JPanel implements PropertyChangeListener, ChangesetAware { 066 067 private ChangesetContentTableModel model; 068 private transient Changeset currentChangeset; 069 070 private DownloadChangesetContentAction actDownloadContentAction; 071 private ShowHistoryAction actShowHistory; 072 private SelectInCurrentLayerAction actSelectInCurrentLayerAction; 073 private ZoomInCurrentLayerAction actZoomInCurrentLayerAction; 074 075 private final HeaderPanel pnlHeader = new HeaderPanel(); 076 public DownloadObjectAction actDownloadObjectAction; 077 078 protected void buildModels() { 079 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); 080 model = new ChangesetContentTableModel(selectionModel); 081 actDownloadContentAction = new DownloadChangesetContentAction(this); 082 actDownloadContentAction.initProperties(); 083 084 actDownloadObjectAction = new DownloadObjectAction(); 085 model.getSelectionModel().addListSelectionListener(actDownloadObjectAction); 086 087 actShowHistory = new ShowHistoryAction(); 088 model.getSelectionModel().addListSelectionListener(actShowHistory); 089 090 actSelectInCurrentLayerAction = new SelectInCurrentLayerAction(); 091 model.getSelectionModel().addListSelectionListener(actSelectInCurrentLayerAction); 092 Main.getLayerManager().addActiveLayerChangeListener(actSelectInCurrentLayerAction); 093 094 actZoomInCurrentLayerAction = new ZoomInCurrentLayerAction(); 095 model.getSelectionModel().addListSelectionListener(actZoomInCurrentLayerAction); 096 Main.getLayerManager().addActiveLayerChangeListener(actZoomInCurrentLayerAction); 097 098 addComponentListener( 099 new ComponentAdapter() { 100 @Override 101 public void componentShown(ComponentEvent e) { 102 Main.getLayerManager().addAndFireActiveLayerChangeListener(actSelectInCurrentLayerAction); 103 Main.getLayerManager().addAndFireActiveLayerChangeListener(actZoomInCurrentLayerAction); 104 } 105 106 @Override 107 public void componentHidden(ComponentEvent e) { 108 // make sure the listener is unregistered when the panel becomes invisible 109 Main.getLayerManager().removeActiveLayerChangeListener(actSelectInCurrentLayerAction); 110 Main.getLayerManager().removeActiveLayerChangeListener(actZoomInCurrentLayerAction); 111 } 112 } 113 ); 114 } 115 116 protected JPanel buildContentPanel() { 117 JPanel pnl = new JPanel(new BorderLayout()); 118 JTable tblContent = new JTable( 119 model, 120 new ChangesetContentTableColumnModel(), 121 model.getSelectionModel() 122 ); 123 tblContent.addMouseListener(new PopupMenuLauncher(new ChangesetContentTablePopupMenu())); 124 pnl.add(new JScrollPane(tblContent), BorderLayout.CENTER); 125 return pnl; 126 } 127 128 protected JPanel buildActionButtonPanel() { 129 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); 130 JToolBar tb = new JToolBar(JToolBar.VERTICAL); 131 tb.setFloatable(false); 132 133 tb.add(actDownloadContentAction); 134 tb.addSeparator(); 135 tb.add(actDownloadObjectAction); 136 tb.add(actShowHistory); 137 tb.addSeparator(); 138 tb.add(actSelectInCurrentLayerAction); 139 tb.add(actZoomInCurrentLayerAction); 140 141 pnl.add(tb); 142 return pnl; 143 } 144 145 protected final void build() { 146 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 147 setLayout(new BorderLayout()); 148 buildModels(); 149 150 add(pnlHeader, BorderLayout.NORTH); 151 add(buildActionButtonPanel(), BorderLayout.WEST); 152 add(buildContentPanel(), BorderLayout.CENTER); 153 } 154 155 /** 156 * Constructs a new {@code ChangesetContentPanel}. 157 */ 158 public ChangesetContentPanel() { 159 build(); 160 } 161 162 /** 163 * Replies the changeset content model 164 * @return The model 165 */ 166 public ChangesetContentTableModel getModel() { 167 return model; 168 } 169 170 protected void setCurrentChangeset(Changeset cs) { 171 currentChangeset = cs; 172 if (cs == null) { 173 model.populate(null); 174 } else { 175 model.populate(cs.getContent()); 176 } 177 actDownloadContentAction.initProperties(); 178 pnlHeader.setChangeset(cs); 179 } 180 181 /* ---------------------------------------------------------------------------- */ 182 /* interface PropertyChangeListener */ 183 /* ---------------------------------------------------------------------------- */ 184 @Override 185 public void propertyChange(PropertyChangeEvent evt) { 186 if (!evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP)) 187 return; 188 Changeset cs = (Changeset) evt.getNewValue(); 189 setCurrentChangeset(cs); 190 } 191 192 private void alertNoPrimitivesTo(Collection<HistoryOsmPrimitive> primitives, String title, String helpTopic) { 193 HelpAwareOptionPane.showOptionDialog( 194 this, 195 trn("<html>The selected object is not available in the current<br>" 196 + "edit layer ''{0}''.</html>", 197 "<html>None of the selected objects is available in the current<br>" 198 + "edit layer ''{0}''.</html>", 199 primitives.size(), 200 Main.getLayerManager().getEditLayer().getName() 201 ), 202 title, JOptionPane.WARNING_MESSAGE, helpTopic 203 ); 204 } 205 206 class ChangesetContentTablePopupMenu extends JPopupMenu { 207 ChangesetContentTablePopupMenu() { 208 add(actDownloadContentAction); 209 add(new JSeparator()); 210 add(actDownloadObjectAction); 211 add(actShowHistory); 212 add(new JSeparator()); 213 add(actSelectInCurrentLayerAction); 214 add(actZoomInCurrentLayerAction); 215 } 216 } 217 218 class ShowHistoryAction extends AbstractAction implements ListSelectionListener { 219 220 private final class ShowHistoryTask implements Runnable { 221 private final Collection<HistoryOsmPrimitive> primitives; 222 223 private ShowHistoryTask(Collection<HistoryOsmPrimitive> primitives) { 224 this.primitives = primitives; 225 } 226 227 @Override 228 public void run() { 229 try { 230 for (HistoryOsmPrimitive p : primitives) { 231 final History h = HistoryDataSet.getInstance().getHistory(p.getPrimitiveId()); 232 if (h == null) { 233 continue; 234 } 235 GuiHelper.runInEDT(new Runnable() { 236 @Override 237 public void run() { 238 HistoryBrowserDialogManager.getInstance().show(h); 239 } 240 }); 241 } 242 } catch (final RuntimeException e) { 243 GuiHelper.runInEDT(new Runnable() { 244 @Override 245 public void run() { 246 BugReportExceptionHandler.handleException(e); 247 } 248 }); 249 } 250 } 251 } 252 253 ShowHistoryAction() { 254 putValue(NAME, tr("Show history")); 255 new ImageProvider("dialogs", "history").getResource().attachImageIcon(this); 256 putValue(SHORT_DESCRIPTION, tr("Download and show the history of the selected objects")); 257 updateEnabledState(); 258 } 259 260 protected List<HistoryOsmPrimitive> filterPrimitivesWithUnloadedHistory(Collection<HistoryOsmPrimitive> primitives) { 261 List<HistoryOsmPrimitive> ret = new ArrayList<>(primitives.size()); 262 for (HistoryOsmPrimitive p: primitives) { 263 if (HistoryDataSet.getInstance().getHistory(p.getPrimitiveId()) == null) { 264 ret.add(p); 265 } 266 } 267 return ret; 268 } 269 270 public void showHistory(final Collection<HistoryOsmPrimitive> primitives) { 271 272 List<HistoryOsmPrimitive> toLoad = filterPrimitivesWithUnloadedHistory(primitives); 273 if (!toLoad.isEmpty()) { 274 HistoryLoadTask task = new HistoryLoadTask(ChangesetContentPanel.this); 275 for (HistoryOsmPrimitive p: toLoad) { 276 task.add(p); 277 } 278 Main.worker.submit(task); 279 } 280 281 Main.worker.submit(new ShowHistoryTask(primitives)); 282 } 283 284 protected final void updateEnabledState() { 285 setEnabled(model.hasSelectedPrimitives()); 286 } 287 288 @Override 289 public void actionPerformed(ActionEvent arg0) { 290 Set<HistoryOsmPrimitive> selected = model.getSelectedPrimitives(); 291 if (selected.isEmpty()) return; 292 showHistory(selected); 293 } 294 295 @Override 296 public void valueChanged(ListSelectionEvent e) { 297 updateEnabledState(); 298 } 299 } 300 301 class DownloadObjectAction extends AbstractAction implements ListSelectionListener { 302 303 DownloadObjectAction() { 304 putValue(NAME, tr("Download objects")); 305 putValue(SMALL_ICON, ImageProvider.get("downloadprimitive")); 306 putValue(SHORT_DESCRIPTION, tr("Download the current version of the selected objects")); 307 updateEnabledState(); 308 } 309 310 @Override 311 public void actionPerformed(ActionEvent arg0) { 312 final List<PrimitiveId> primitiveIds = new ArrayList<>(Utils.transform( 313 model.getSelectedPrimitives(), new Utils.Function<HistoryOsmPrimitive, PrimitiveId>() { 314 @Override 315 public PrimitiveId apply(HistoryOsmPrimitive x) { 316 return x.getPrimitiveId(); 317 } 318 })); 319 Main.worker.submit(new DownloadPrimitivesWithReferrersTask(false, primitiveIds, true, true, null, null)); 320 } 321 322 protected final void updateEnabledState() { 323 setEnabled(model.hasSelectedPrimitives()); 324 } 325 326 @Override 327 public void valueChanged(ListSelectionEvent e) { 328 updateEnabledState(); 329 } 330 } 331 332 abstract class SelectionBasedAction extends AbstractAction implements ListSelectionListener, ActiveLayerChangeListener { 333 334 protected Set<OsmPrimitive> getTarget() { 335 if (!isEnabled()) { 336 return null; 337 } 338 OsmDataLayer layer = Main.getLayerManager().getEditLayer(); 339 if (layer == null) { 340 return null; 341 } 342 Set<OsmPrimitive> target = new HashSet<>(); 343 for (HistoryOsmPrimitive p : model.getSelectedPrimitives()) { 344 OsmPrimitive op = layer.data.getPrimitiveById(p.getPrimitiveId()); 345 if (op != null) { 346 target.add(op); 347 } 348 } 349 return target; 350 } 351 352 public final void updateEnabledState() { 353 setEnabled(Main.getLayerManager().getEditLayer() != null && model.hasSelectedPrimitives()); 354 } 355 356 @Override 357 public void valueChanged(ListSelectionEvent e) { 358 updateEnabledState(); 359 } 360 361 @Override 362 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 363 updateEnabledState(); 364 } 365 366 } 367 368 class SelectInCurrentLayerAction extends SelectionBasedAction { 369 370 SelectInCurrentLayerAction() { 371 putValue(NAME, tr("Select in layer")); 372 new ImageProvider("dialogs", "select").getResource().attachImageIcon(this); 373 putValue(SHORT_DESCRIPTION, tr("Select the corresponding primitives in the current data layer")); 374 updateEnabledState(); 375 } 376 377 @Override 378 public void actionPerformed(ActionEvent arg0) { 379 final Set<OsmPrimitive> target = getTarget(); 380 if (target == null) { 381 return; 382 } else if (target.isEmpty()) { 383 alertNoPrimitivesTo(model.getSelectedPrimitives(), tr("Nothing to select"), 384 HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToSelectInLayer")); 385 return; 386 } 387 Main.getLayerManager().getEditLayer().data.setSelected(target); 388 } 389 } 390 391 class ZoomInCurrentLayerAction extends SelectionBasedAction { 392 393 ZoomInCurrentLayerAction() { 394 putValue(NAME, tr("Zoom to in layer")); 395 new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this); 396 putValue(SHORT_DESCRIPTION, tr("Zoom to the corresponding objects in the current data layer")); 397 updateEnabledState(); 398 } 399 400 @Override 401 public void actionPerformed(ActionEvent arg0) { 402 final Set<OsmPrimitive> target = getTarget(); 403 if (target == null) { 404 return; 405 } else if (target.isEmpty()) { 406 alertNoPrimitivesTo(model.getSelectedPrimitives(), tr("Nothing to zoom to"), 407 HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToZoomTo")); 408 return; 409 } 410 Main.getLayerManager().getEditLayer().data.setSelected(target); 411 AutoScaleAction.zoomToSelection(); 412 } 413 } 414 415 private static class HeaderPanel extends JPanel { 416 417 private JMultilineLabel lblMessage; 418 private transient Changeset current; 419 420 protected final void build() { 421 setLayout(new FlowLayout(FlowLayout.LEFT)); 422 lblMessage = new JMultilineLabel( 423 tr("The content of this changeset is not downloaded yet.") 424 ); 425 add(lblMessage); 426 add(new JButton(new DownloadAction())); 427 428 } 429 430 HeaderPanel() { 431 build(); 432 } 433 434 public void setChangeset(Changeset cs) { 435 setVisible(cs != null && cs.getContent() == null); 436 this.current = cs; 437 } 438 439 private class DownloadAction extends AbstractAction { 440 DownloadAction() { 441 putValue(NAME, tr("Download now")); 442 putValue(SHORT_DESCRIPTION, tr("Download the changeset content")); 443 new ImageProvider("dialogs/changeset", "downloadchangesetcontent").getResource().attachImageIcon(this); 444 } 445 446 @Override 447 public void actionPerformed(ActionEvent evt) { 448 if (current == null) return; 449 ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(HeaderPanel.this, current.getId()); 450 ChangesetCacheManager.getInstance().runDownloadTask(task); 451 } 452 } 453 } 454 455 @Override 456 public Changeset getCurrentChangeset() { 457 return currentChangeset; 458 } 459}