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