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