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