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.trc; 006 007import java.awt.BorderLayout; 008import java.awt.FlowLayout; 009import java.awt.GridBagConstraints; 010import java.awt.GridBagLayout; 011import java.awt.Insets; 012import java.awt.event.ActionEvent; 013import java.awt.event.ComponentAdapter; 014import java.awt.event.ComponentEvent; 015import java.beans.PropertyChangeEvent; 016import java.beans.PropertyChangeListener; 017import java.text.DateFormat; 018import java.util.Collection; 019import java.util.Collections; 020import java.util.HashSet; 021import java.util.Set; 022 023import javax.swing.AbstractAction; 024import javax.swing.BorderFactory; 025import javax.swing.JLabel; 026import javax.swing.JOptionPane; 027import javax.swing.JPanel; 028import javax.swing.JToolBar; 029 030import org.openstreetmap.josm.Main; 031import org.openstreetmap.josm.actions.AutoScaleAction; 032import org.openstreetmap.josm.data.osm.Changeset; 033import org.openstreetmap.josm.data.osm.ChangesetCache; 034import org.openstreetmap.josm.data.osm.OsmPrimitive; 035import org.openstreetmap.josm.gui.HelpAwareOptionPane; 036import org.openstreetmap.josm.gui.MapView; 037import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener; 038import org.openstreetmap.josm.gui.help.HelpUtil; 039import org.openstreetmap.josm.gui.layer.OsmDataLayer; 040import org.openstreetmap.josm.gui.widgets.JosmTextArea; 041import org.openstreetmap.josm.gui.widgets.JosmTextField; 042import org.openstreetmap.josm.tools.ImageProvider; 043 044 045/** 046 * This panel displays the properties of the currently selected changeset in the 047 * {@link ChangesetCacheManager}. 048 * 049 */ 050public class ChangesetDetailPanel extends JPanel implements PropertyChangeListener{ 051 052 private JosmTextField tfID; 053 private JosmTextArea taComment; 054 private JosmTextField tfOpen; 055 private JosmTextField tfUser; 056 private JosmTextField tfCreatedOn; 057 private JosmTextField tfClosedOn; 058 private DonwloadChangesetContentAction actDownloadChangesetContent; 059 private UpdateChangesetAction actUpdateChangesets; 060 private RemoveFromCacheAction actRemoveFromCache; 061 private SelectInCurrentLayerAction actSelectInCurrentLayer; 062 private ZoomInCurrentLayerAction actZoomInCurrentLayerAction; 063 064 private Changeset current = null; 065 066 protected JPanel buildActionButtonPanel() { 067 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); 068 069 JToolBar tb = new JToolBar(JToolBar.VERTICAL); 070 tb.setFloatable(false); 071 072 // -- remove from cache action 073 tb.add(actRemoveFromCache = new RemoveFromCacheAction()); 074 actRemoveFromCache.initProperties(current); 075 076 // -- changeset update 077 tb.add(actUpdateChangesets = new UpdateChangesetAction()); 078 actUpdateChangesets.initProperties(current); 079 080 // -- changeset content download 081 tb.add(actDownloadChangesetContent =new DonwloadChangesetContentAction()); 082 actDownloadChangesetContent.initProperties(current); 083 084 tb.add(actSelectInCurrentLayer = new SelectInCurrentLayerAction()); 085 MapView.addEditLayerChangeListener(actSelectInCurrentLayer); 086 087 tb.add(actZoomInCurrentLayerAction = new ZoomInCurrentLayerAction()); 088 MapView.addEditLayerChangeListener(actZoomInCurrentLayerAction); 089 090 addComponentListener( 091 new ComponentAdapter() { 092 @Override 093 public void componentHidden(ComponentEvent e) { 094 // make sure the listener is unregistered when the panel becomes 095 // invisible 096 MapView.removeEditLayerChangeListener(actSelectInCurrentLayer); 097 MapView.removeEditLayerChangeListener(actZoomInCurrentLayerAction); 098 } 099 } 100 ); 101 102 pnl.add(tb); 103 return pnl; 104 } 105 106 protected JPanel buildDetailViewPanel() { 107 JPanel pnl = new JPanel(new GridBagLayout()); 108 109 GridBagConstraints gc = new GridBagConstraints(); 110 gc.anchor = GridBagConstraints.FIRST_LINE_START; 111 gc.insets = new Insets(0,0,2,3); 112 113 //-- id 114 gc.fill = GridBagConstraints.HORIZONTAL; 115 gc.weightx = 0.0; 116 pnl.add(new JLabel(tr("ID:")), gc); 117 118 gc.fill = GridBagConstraints.HORIZONTAL; 119 gc.weightx = 0.0; 120 gc.gridx = 1; 121 pnl.add(tfID = new JosmTextField(10), gc); 122 tfID.setEditable(false); 123 124 //-- comment 125 gc.gridx = 0; 126 gc.gridy = 1; 127 gc.fill = GridBagConstraints.HORIZONTAL; 128 gc.weightx = 0.0; 129 pnl.add(new JLabel(tr("Comment:")), gc); 130 131 gc.fill = GridBagConstraints.BOTH; 132 gc.weightx = 1.0; 133 gc.weighty = 1.0; 134 gc.gridx = 1; 135 pnl.add(taComment= new JosmTextArea(5,40), gc); 136 taComment.setEditable(false); 137 138 //-- Open/Closed 139 gc.gridx = 0; 140 gc.gridy = 2; 141 gc.fill = GridBagConstraints.HORIZONTAL; 142 gc.weightx = 0.0; 143 gc.weighty = 0.0; 144 pnl.add(new JLabel(tr("Open/Closed:")), gc); 145 146 gc.fill = GridBagConstraints.HORIZONTAL; 147 gc.gridx = 1; 148 pnl.add(tfOpen= new JosmTextField(10), gc); 149 tfOpen.setEditable(false); 150 151 //-- Created by: 152 gc.gridx = 0; 153 gc.gridy = 3; 154 gc.fill = GridBagConstraints.HORIZONTAL; 155 gc.weightx = 0.0; 156 pnl.add(new JLabel(tr("Created by:")), gc); 157 158 gc.fill = GridBagConstraints.HORIZONTAL; 159 gc.weightx = 1.0; 160 gc.gridx = 1; 161 pnl.add(tfUser= new JosmTextField(""), gc); 162 tfUser.setEditable(false); 163 164 //-- Created On: 165 gc.gridx = 0; 166 gc.gridy = 4; 167 gc.fill = GridBagConstraints.HORIZONTAL; 168 gc.weightx = 0.0; 169 pnl.add(new JLabel(tr("Created on:")), gc); 170 171 gc.fill = GridBagConstraints.HORIZONTAL; 172 gc.gridx = 1; 173 pnl.add(tfCreatedOn= new JosmTextField(20), gc); 174 tfCreatedOn.setEditable(false); 175 176 //-- Closed On: 177 gc.gridx = 0; 178 gc.gridy = 5; 179 gc.fill = GridBagConstraints.HORIZONTAL; 180 gc.weightx = 0.0; 181 pnl.add(new JLabel(tr("Closed on:")), gc); 182 183 gc.fill = GridBagConstraints.HORIZONTAL; 184 gc.gridx = 1; 185 pnl.add(tfClosedOn= new JosmTextField(20), gc); 186 tfClosedOn.setEditable(false); 187 188 return pnl; 189 } 190 191 protected void build() { 192 setLayout(new BorderLayout()); 193 setBorder(BorderFactory.createEmptyBorder(3,3,3,3)); 194 add(buildDetailViewPanel(), BorderLayout.CENTER); 195 add(buildActionButtonPanel(), BorderLayout.WEST); 196 } 197 198 protected void clearView() { 199 tfID.setText(""); 200 taComment.setText(""); 201 tfOpen.setText(""); 202 tfUser.setText(""); 203 tfCreatedOn.setText(""); 204 tfClosedOn.setText(""); 205 } 206 207 protected void updateView(Changeset cs) { 208 String msg; 209 if (cs == null) return; 210 tfID.setText(Integer.toString(cs.getId())); 211 String comment = cs.get("comment"); 212 taComment.setText(comment == null ? "" : comment); 213 214 if (cs.isOpen()) { 215 msg = trc("changeset.state", "Open"); 216 } else { 217 msg = trc("changeset.state", "Closed"); 218 } 219 tfOpen.setText(msg); 220 221 if (cs.getUser() == null) { 222 msg = tr("anonymous"); 223 } else { 224 msg = cs.getUser().getName(); 225 } 226 tfUser.setText(msg); 227 DateFormat sdf = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); 228 229 tfCreatedOn.setText(cs.getCreatedAt() == null ? "" : sdf.format(cs.getCreatedAt())); 230 tfClosedOn.setText(cs.getClosedAt() == null ? "" : sdf.format(cs.getClosedAt())); 231 } 232 233 /** 234 * Constructs a new {@code ChangesetDetailPanel}. 235 */ 236 public ChangesetDetailPanel() { 237 build(); 238 } 239 240 protected void setCurrentChangeset(Changeset cs) { 241 current = cs; 242 if (cs == null) { 243 clearView(); 244 } else { 245 updateView(cs); 246 } 247 actDownloadChangesetContent.initProperties(current); 248 actUpdateChangesets.initProperties(current); 249 actRemoveFromCache.initProperties(current); 250 actSelectInCurrentLayer.updateEnabledState(); 251 actZoomInCurrentLayerAction.updateEnabledState(); 252 } 253 254 /* ---------------------------------------------------------------------------- */ 255 /* interface PropertyChangeListener */ 256 /* ---------------------------------------------------------------------------- */ 257 @Override 258 public void propertyChange(PropertyChangeEvent evt) { 259 if (! evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP)) 260 return; 261 Changeset cs = (Changeset)evt.getNewValue(); 262 setCurrentChangeset(cs); 263 } 264 265 /** 266 * The action for removing the currently selected changeset from the changeset cache 267 */ 268 class RemoveFromCacheAction extends AbstractAction { 269 public RemoveFromCacheAction() { 270 putValue(NAME, tr("Remove from cache")); 271 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete")); 272 putValue(SHORT_DESCRIPTION, tr("Remove the changeset in the detail view panel from the local cache")); 273 } 274 275 @Override 276 public void actionPerformed(ActionEvent evt) { 277 if (current == null) 278 return; 279 ChangesetCache.getInstance().remove(current); 280 } 281 282 public void initProperties(Changeset cs) { 283 setEnabled(cs != null); 284 } 285 } 286 287 /** 288 * Removes the selected changesets from the local changeset cache 289 * 290 */ 291 class DonwloadChangesetContentAction extends AbstractAction{ 292 public DonwloadChangesetContentAction() { 293 putValue(NAME, tr("Download content")); 294 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent")); 295 putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server")); 296 } 297 298 @Override 299 public void actionPerformed(ActionEvent evt) { 300 if (current == null) return; 301 ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetDetailPanel.this,current.getId()); 302 ChangesetCacheManager.getInstance().runDownloadTask(task); 303 } 304 305 public void initProperties(Changeset cs) { 306 if (cs == null) { 307 setEnabled(false); 308 return; 309 } else { 310 setEnabled(true); 311 } 312 if (cs.getContent() == null) { 313 putValue(NAME, tr("Download content")); 314 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent")); 315 putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server")); 316 } else { 317 putValue(NAME, tr("Update content")); 318 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","updatechangesetcontent")); 319 putValue(SHORT_DESCRIPTION, tr("Update the changeset content from the OSM server")); 320 } 321 } 322 } 323 324 /** 325 * Updates the current changeset from the OSM server 326 * 327 */ 328 class UpdateChangesetAction extends AbstractAction{ 329 public UpdateChangesetAction() { 330 putValue(NAME, tr("Update changeset")); 331 putValue(SMALL_ICON,ImageProvider.get("dialogs/changeset","updatechangeset")); 332 putValue(SHORT_DESCRIPTION, tr("Update the changeset from the OSM server")); 333 } 334 335 @Override 336 public void actionPerformed(ActionEvent evt) { 337 if (current == null) return; 338 Main.worker.submit( 339 new ChangesetHeaderDownloadTask( 340 ChangesetDetailPanel.this, 341 Collections.singleton(current.getId()) 342 ) 343 ); 344 } 345 346 public void initProperties(Changeset cs) { 347 if (cs == null) { 348 setEnabled(false); 349 return; 350 } else { 351 setEnabled(true); 352 } 353 } 354 } 355 356 /** 357 * Selects the primitives in the content of this changeset in the current 358 * data layer. 359 * 360 */ 361 class SelectInCurrentLayerAction extends AbstractAction implements EditLayerChangeListener{ 362 363 public SelectInCurrentLayerAction() { 364 putValue(NAME, tr("Select in layer")); 365 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select")); 366 putValue(SHORT_DESCRIPTION, tr("Select the primitives in the content of this changeset in the current data layer")); 367 updateEnabledState(); 368 } 369 370 protected void alertNoPrimitivesToSelect(Collection<OsmPrimitive> primitives) { 371 HelpAwareOptionPane.showOptionDialog( 372 ChangesetDetailPanel.this, 373 tr("<html>None of the objects in the content of changeset {0} is available in the current<br>" 374 + "edit layer ''{1}''.</html>", 375 current.getId(), 376 Main.main.getEditLayer().getName() 377 ), 378 tr("Nothing to select"), 379 JOptionPane.WARNING_MESSAGE, 380 HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToSelectInLayer") 381 ); 382 } 383 384 @Override 385 public void actionPerformed(ActionEvent arg0) { 386 if (!isEnabled()) 387 return; 388 if (Main.main == null || !Main.main.hasEditLayer()) return; 389 OsmDataLayer layer = Main.main.getEditLayer(); 390 Set<OsmPrimitive> target = new HashSet<OsmPrimitive>(); 391 for (OsmPrimitive p: layer.data.allPrimitives()) { 392 if (p.isUsable() && p.getChangesetId() == current.getId()) { 393 target.add(p); 394 } 395 } 396 if (target.isEmpty()) { 397 alertNoPrimitivesToSelect(target); 398 return; 399 } 400 layer.data.setSelected(target); 401 } 402 403 public void updateEnabledState() { 404 if (Main.main == null || !Main.main.hasEditLayer()) { 405 setEnabled(false); 406 return; 407 } 408 setEnabled(current != null); 409 } 410 411 @Override 412 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 413 updateEnabledState(); 414 } 415 } 416 417 /** 418 * Zooms to the primitives in the content of this changeset in the current 419 * data layer. 420 * 421 */ 422 class ZoomInCurrentLayerAction extends AbstractAction implements EditLayerChangeListener{ 423 424 public ZoomInCurrentLayerAction() { 425 putValue(NAME, tr("Zoom to in layer")); 426 putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection")); 427 putValue(SHORT_DESCRIPTION, tr("Zoom to the objects in the content of this changeset in the current data layer")); 428 updateEnabledState(); 429 } 430 431 protected void alertNoPrimitivesToZoomTo() { 432 HelpAwareOptionPane.showOptionDialog( 433 ChangesetDetailPanel.this, 434 tr("<html>None of the objects in the content of changeset {0} is available in the current<br>" 435 + "edit layer ''{1}''.</html>", 436 current.getId(), 437 Main.main.getEditLayer().getName() 438 ), 439 tr("Nothing to zoom to"), 440 JOptionPane.WARNING_MESSAGE, 441 HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToZoomTo") 442 ); 443 } 444 445 @Override 446 public void actionPerformed(ActionEvent arg0) { 447 if (!isEnabled()) 448 return; 449 if (Main.main == null || !Main.main.hasEditLayer()) return; 450 OsmDataLayer layer = Main.main.getEditLayer(); 451 Set<OsmPrimitive> target = new HashSet<OsmPrimitive>(); 452 for (OsmPrimitive p: layer.data.allPrimitives()) { 453 if (p.isUsable() && p.getChangesetId() == current.getId()) { 454 target.add(p); 455 } 456 } 457 if (target.isEmpty()) { 458 alertNoPrimitivesToZoomTo(); 459 return; 460 } 461 layer.data.setSelected(target); 462 AutoScaleAction.zoomToSelection(); 463 } 464 465 public void updateEnabledState() { 466 if (Main.main == null || !Main.main.hasEditLayer()) { 467 setEnabled(false); 468 return; 469 } 470 setEnabled(current != null); 471 } 472 473 @Override 474 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 475 updateEnabledState(); 476 } 477 } 478}