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 scollable 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 Changeset currentChangeset; 065 066 private DonwloadChangesetContentAction actDownloadContentAction; 067 private ShowHistoryAction actShowHistory; 068 private SelectInCurrentLayerAction actSelectInCurrentLayerAction; 069 private ZoomInCurrentLayerAction actZoomInCurrentLayerAction; 070 071 private HeaderPanel pnlHeader; 072 073 protected void buildModels() { 074 DefaultListSelectionModel selectionModel =new DefaultListSelectionModel(); 075 model = new ChangesetContentTableModel(selectionModel); 076 actDownloadContentAction = new DonwloadChangesetContentAction(); 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 void build() { 129 setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); 130 setLayout(new BorderLayout()); 131 buildModels(); 132 133 add(pnlHeader = new HeaderPanel(), BorderLayout.NORTH); 134 add(buildActionButtonPanel(), BorderLayout.WEST); 135 add(buildContentPanel(), BorderLayout.CENTER); 136 137 } 138 139 /** 140 * Constructs a new {@code ChangesetContentPanel}. 141 */ 142 public ChangesetContentPanel() { 143 build(); 144 } 145 146 /** 147 * Replies the changeset content model 148 * @return The model 149 */ 150 public ChangesetContentTableModel getModel() { 151 return model; 152 } 153 154 protected void setCurrentChangeset(Changeset cs) { 155 currentChangeset = cs; 156 if (cs == null) { 157 model.populate(null); 158 } else { 159 model.populate(cs.getContent()); 160 } 161 actDownloadContentAction.initProperties(cs); 162 pnlHeader.setChangeset(cs); 163 } 164 165 /* ---------------------------------------------------------------------------- */ 166 /* interface PropertyChangeListener */ 167 /* ---------------------------------------------------------------------------- */ 168 @Override 169 public void propertyChange(PropertyChangeEvent evt) { 170 if(!evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP)) 171 return; 172 Changeset cs = (Changeset)evt.getNewValue(); 173 setCurrentChangeset(cs); 174 } 175 176 /** 177 * Downloads/Updates the content of the changeset 178 * 179 */ 180 class DonwloadChangesetContentAction extends AbstractAction{ 181 public DonwloadChangesetContentAction() { 182 putValue(NAME, tr("Download content")); 183 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent")); 184 putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server")); 185 } 186 187 @Override 188 public void actionPerformed(ActionEvent evt) { 189 if (currentChangeset == null) return; 190 ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetContentPanel.this,currentChangeset.getId()); 191 ChangesetCacheManager.getInstance().runDownloadTask(task); 192 } 193 194 public void initProperties(Changeset cs) { 195 if (cs == null) { 196 setEnabled(false); 197 return; 198 } else { 199 setEnabled(true); 200 } 201 if (cs.getContent() == null) { 202 putValue(NAME, tr("Download content")); 203 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent")); 204 putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server")); 205 } else { 206 putValue(NAME, tr("Update content")); 207 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","updatechangesetcontent")); 208 putValue(SHORT_DESCRIPTION, tr("Update the changeset content from the OSM server")); 209 } 210 } 211 } 212 213 class ChangesetContentTablePopupMenu extends JPopupMenu { 214 public ChangesetContentTablePopupMenu() { 215 add(actDownloadContentAction); 216 add(actShowHistory); 217 add(new JSeparator()); 218 add(actSelectInCurrentLayerAction); 219 add(actZoomInCurrentLayerAction); 220 } 221 } 222 223 class ShowHistoryAction extends AbstractAction implements ListSelectionListener{ 224 public ShowHistoryAction() { 225 putValue(NAME, tr("Show history")); 226 putValue(SMALL_ICON, ImageProvider.get("dialogs", "history")); 227 putValue(SHORT_DESCRIPTION, tr("Download and show the history of the selected objects")); 228 updateEnabledState(); 229 } 230 231 protected List<HistoryOsmPrimitive> filterPrimitivesWithUnloadedHistory(Collection<HistoryOsmPrimitive> primitives) { 232 List<HistoryOsmPrimitive> ret = new ArrayList<HistoryOsmPrimitive>(primitives.size()); 233 for (HistoryOsmPrimitive p: primitives) { 234 if (HistoryDataSet.getInstance().getHistory(p.getPrimitiveId()) == null) { 235 ret.add(p); 236 } 237 } 238 return ret; 239 } 240 241 public void showHistory(final Collection<HistoryOsmPrimitive> primitives) { 242 243 List<HistoryOsmPrimitive> toLoad = filterPrimitivesWithUnloadedHistory(primitives); 244 if (!toLoad.isEmpty()) { 245 HistoryLoadTask task = new HistoryLoadTask(ChangesetContentPanel.this); 246 for (HistoryOsmPrimitive p: toLoad) { 247 task.add(p); 248 } 249 Main.worker.submit(task); 250 } 251 252 Runnable r = new Runnable() { 253 @Override 254 public void run() { 255 try { 256 for (HistoryOsmPrimitive p : primitives) { 257 final History h = HistoryDataSet.getInstance().getHistory(p.getPrimitiveId()); 258 if (h == null) { 259 continue; 260 } 261 GuiHelper.runInEDT(new Runnable() { 262 @Override public void run() { 263 HistoryBrowserDialogManager.getInstance().show(h); 264 } 265 }); 266 } 267 } catch (final Exception e) { 268 GuiHelper.runInEDT(new Runnable() { 269 @Override 270 public void run() { 271 BugReportExceptionHandler.handleException(e); 272 } 273 }); 274 } 275 276 } 277 }; 278 Main.worker.submit(r); 279 } 280 281 protected void updateEnabledState() { 282 setEnabled(model.hasSelectedPrimitives()); 283 } 284 285 @Override 286 public void actionPerformed(ActionEvent arg0) { 287 Set<HistoryOsmPrimitive> selected = model.getSelectedPrimitives(); 288 if (selected.isEmpty()) return; 289 showHistory(selected); 290 } 291 292 @Override 293 public void valueChanged(ListSelectionEvent e) { 294 updateEnabledState(); 295 } 296 } 297 298 class SelectInCurrentLayerAction extends AbstractAction implements ListSelectionListener, EditLayerChangeListener{ 299 300 public SelectInCurrentLayerAction() { 301 putValue(NAME, tr("Select in layer")); 302 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select")); 303 putValue(SHORT_DESCRIPTION, tr("Select the corresponding primitives in the current data layer")); 304 updateEnabledState(); 305 } 306 307 protected void alertNoPrimitivesToSelect(Collection<HistoryOsmPrimitive> primitives) { 308 HelpAwareOptionPane.showOptionDialog( 309 ChangesetContentPanel.this, 310 trn("<html>The selected object is not available in the current<br>" 311 + "edit layer ''{0}''.</html>", 312 "<html>None of the selected objects is available in the current<br>" 313 + "edit layer ''{0}''.</html>", 314 primitives.size(), 315 Main.main.getEditLayer().getName() 316 ), 317 tr("Nothing to select"), 318 JOptionPane.WARNING_MESSAGE, 319 HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToSelectInLayer") 320 ); 321 } 322 323 @Override 324 public void actionPerformed(ActionEvent arg0) { 325 if (!isEnabled()) 326 return; 327 if (Main.main == null || !Main.main.hasEditLayer()) return; 328 OsmDataLayer layer = Main.main.getEditLayer(); 329 Set<HistoryOsmPrimitive> selected = model.getSelectedPrimitives(); 330 Set<OsmPrimitive> target = new HashSet<OsmPrimitive>(); 331 for (HistoryOsmPrimitive p : model.getSelectedPrimitives()) { 332 OsmPrimitive op = layer.data.getPrimitiveById(p.getPrimitiveId()); 333 if (op != null) { 334 target.add(op); 335 } 336 } 337 if (target.isEmpty()) { 338 alertNoPrimitivesToSelect(selected); 339 return; 340 } 341 layer.data.setSelected(target); 342 } 343 344 public 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 class ZoomInCurrentLayerAction extends AbstractAction implements ListSelectionListener, EditLayerChangeListener{ 364 365 public ZoomInCurrentLayerAction() { 366 putValue(NAME, tr("Zoom to in layer")); 367 putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection")); 368 putValue(SHORT_DESCRIPTION, tr("Zoom to the corresponding objects in the current data layer")); 369 updateEnabledState(); 370 } 371 372 protected void alertNoPrimitivesToZoomTo(Collection<HistoryOsmPrimitive> primitives) { 373 HelpAwareOptionPane.showOptionDialog( 374 ChangesetContentPanel.this, 375 trn("<html>The selected object is not available in the current<br>" 376 + "edit layer ''{0}''.</html>", 377 "<html>None of the selected objects is available in the current<br>" 378 + "edit layer ''{0}''.</html>", 379 primitives.size(), 380 Main.main.getEditLayer().getName() 381 ), 382 tr("Nothing to zoom to"), 383 JOptionPane.WARNING_MESSAGE, 384 HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToZoomTo") 385 ); 386 } 387 388 @Override 389 public void actionPerformed(ActionEvent arg0) { 390 if (!isEnabled()) 391 return; 392 if (Main.main == null || !Main.main.hasEditLayer()) return; 393 OsmDataLayer layer = Main.main.getEditLayer(); 394 Set<HistoryOsmPrimitive> selected = model.getSelectedPrimitives(); 395 Set<OsmPrimitive> target = new HashSet<OsmPrimitive>(); 396 for (HistoryOsmPrimitive p : model.getSelectedPrimitives()) { 397 OsmPrimitive op = layer.data.getPrimitiveById(p.getPrimitiveId()); 398 if (op != null) { 399 target.add(op); 400 } 401 } 402 if (target.isEmpty()) { 403 alertNoPrimitivesToZoomTo(selected); 404 return; 405 } 406 layer.data.setSelected(target); 407 AutoScaleAction.zoomToSelection(); 408 } 409 410 public void updateEnabledState() { 411 if (Main.main == null || !Main.main.hasEditLayer()) { 412 setEnabled(false); 413 return; 414 } 415 setEnabled(model.hasSelectedPrimitives()); 416 } 417 418 @Override 419 public void valueChanged(ListSelectionEvent e) { 420 updateEnabledState(); 421 } 422 423 @Override 424 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 425 updateEnabledState(); 426 } 427 } 428 429 static private class HeaderPanel extends JPanel { 430 431 private JMultilineLabel lblMessage; 432 private Changeset current; 433 434 protected void build() { 435 setLayout(new FlowLayout(FlowLayout.LEFT)); 436 lblMessage = new JMultilineLabel( 437 tr("The content of this changeset is not downloaded yet.") 438 ); 439 add(lblMessage); 440 add(new JButton(new DownloadAction())); 441 442 } 443 444 public HeaderPanel() { 445 build(); 446 } 447 448 public void setChangeset(Changeset cs) { 449 setVisible(cs != null && cs.getContent() == null); 450 this.current = cs; 451 } 452 453 private class DownloadAction extends AbstractAction { 454 public DownloadAction() { 455 putValue(NAME, tr("Download now")); 456 putValue(SHORT_DESCRIPTION, tr("Download the changeset content")); 457 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangesetcontent")); 458 } 459 460 @Override 461 public void actionPerformed(ActionEvent evt) { 462 if (current == null) return; 463 ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(HeaderPanel.this, current.getId()); 464 ChangesetCacheManager.getInstance().runDownloadTask(task); 465 } 466 } 467 } 468}