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