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