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; 043import org.openstreetmap.josm.tools.date.DateUtils; 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 final JosmTextField tfID = new JosmTextField(10); 053 private final JosmTextArea taComment = new JosmTextArea(5,40); 054 private final JosmTextField tfOpen = new JosmTextField(10); 055 private final JosmTextField tfUser = new JosmTextField(""); 056 private final JosmTextField tfCreatedOn = new JosmTextField(20); 057 private final JosmTextField tfClosedOn = new JosmTextField(20); 058 059 private final DownloadChangesetContentAction actDownloadChangesetContent = new DownloadChangesetContentAction(); 060 private final UpdateChangesetAction actUpdateChangesets = new UpdateChangesetAction(); 061 private final RemoveFromCacheAction actRemoveFromCache = new RemoveFromCacheAction(); 062 private final SelectInCurrentLayerAction actSelectInCurrentLayer = new SelectInCurrentLayerAction(); 063 private final ZoomInCurrentLayerAction actZoomInCurrentLayerAction = new ZoomInCurrentLayerAction(); 064 065 private Changeset current = null; 066 067 protected JPanel buildActionButtonPanel() { 068 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); 069 070 JToolBar tb = new JToolBar(JToolBar.VERTICAL); 071 tb.setFloatable(false); 072 073 // -- remove from cache action 074 tb.add(actRemoveFromCache); 075 actRemoveFromCache.initProperties(current); 076 077 // -- changeset update 078 tb.add(actUpdateChangesets); 079 actUpdateChangesets.initProperties(current); 080 081 // -- changeset content download 082 tb.add(actDownloadChangesetContent); 083 actDownloadChangesetContent.initProperties(current); 084 085 tb.add(actSelectInCurrentLayer); 086 MapView.addEditLayerChangeListener(actSelectInCurrentLayer); 087 088 tb.add(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(actSelectInCurrentLayer); 098 MapView.removeEditLayerChangeListener(actZoomInCurrentLayerAction); 099 } 100 } 101 ); 102 103 pnl.add(tb); 104 return pnl; 105 } 106 107 protected JPanel buildDetailViewPanel() { 108 JPanel pnl = new JPanel(new GridBagLayout()); 109 110 GridBagConstraints gc = new GridBagConstraints(); 111 gc.anchor = GridBagConstraints.FIRST_LINE_START; 112 gc.insets = new Insets(0,0,2,3); 113 114 //-- id 115 gc.fill = GridBagConstraints.HORIZONTAL; 116 gc.weightx = 0.0; 117 pnl.add(new JLabel(tr("ID:")), gc); 118 119 gc.fill = GridBagConstraints.HORIZONTAL; 120 gc.weightx = 0.0; 121 gc.gridx = 1; 122 pnl.add(tfID, gc); 123 tfID.setEditable(false); 124 125 //-- comment 126 gc.gridx = 0; 127 gc.gridy = 1; 128 gc.fill = GridBagConstraints.HORIZONTAL; 129 gc.weightx = 0.0; 130 pnl.add(new JLabel(tr("Comment:")), gc); 131 132 gc.fill = GridBagConstraints.BOTH; 133 gc.weightx = 1.0; 134 gc.weighty = 1.0; 135 gc.gridx = 1; 136 pnl.add(taComment, gc); 137 taComment.setEditable(false); 138 139 //-- Open/Closed 140 gc.gridx = 0; 141 gc.gridy = 2; 142 gc.fill = GridBagConstraints.HORIZONTAL; 143 gc.weightx = 0.0; 144 gc.weighty = 0.0; 145 pnl.add(new JLabel(tr("Open/Closed:")), gc); 146 147 gc.fill = GridBagConstraints.HORIZONTAL; 148 gc.gridx = 1; 149 pnl.add(tfOpen, gc); 150 tfOpen.setEditable(false); 151 152 //-- Created by: 153 gc.gridx = 0; 154 gc.gridy = 3; 155 gc.fill = GridBagConstraints.HORIZONTAL; 156 gc.weightx = 0.0; 157 pnl.add(new JLabel(tr("Created by:")), gc); 158 159 gc.fill = GridBagConstraints.HORIZONTAL; 160 gc.weightx = 1.0; 161 gc.gridx = 1; 162 pnl.add(tfUser, gc); 163 tfUser.setEditable(false); 164 165 //-- Created On: 166 gc.gridx = 0; 167 gc.gridy = 4; 168 gc.fill = GridBagConstraints.HORIZONTAL; 169 gc.weightx = 0.0; 170 pnl.add(new JLabel(tr("Created on:")), gc); 171 172 gc.fill = GridBagConstraints.HORIZONTAL; 173 gc.gridx = 1; 174 pnl.add(tfCreatedOn, gc); 175 tfCreatedOn.setEditable(false); 176 177 //-- Closed On: 178 gc.gridx = 0; 179 gc.gridy = 5; 180 gc.fill = GridBagConstraints.HORIZONTAL; 181 gc.weightx = 0.0; 182 pnl.add(new JLabel(tr("Closed on:")), gc); 183 184 gc.fill = GridBagConstraints.HORIZONTAL; 185 gc.gridx = 1; 186 pnl.add(tfClosedOn, gc); 187 tfClosedOn.setEditable(false); 188 189 return pnl; 190 } 191 192 protected final void build() { 193 setLayout(new BorderLayout()); 194 setBorder(BorderFactory.createEmptyBorder(3,3,3,3)); 195 add(buildDetailViewPanel(), BorderLayout.CENTER); 196 add(buildActionButtonPanel(), BorderLayout.WEST); 197 } 198 199 protected void clearView() { 200 tfID.setText(""); 201 taComment.setText(""); 202 tfOpen.setText(""); 203 tfUser.setText(""); 204 tfCreatedOn.setText(""); 205 tfClosedOn.setText(""); 206 } 207 208 protected void updateView(Changeset cs) { 209 String msg; 210 if (cs == null) return; 211 tfID.setText(Integer.toString(cs.getId())); 212 String comment = cs.get("comment"); 213 taComment.setText(comment == null ? "" : comment); 214 215 if (cs.isOpen()) { 216 msg = trc("changeset.state", "Open"); 217 } else { 218 msg = trc("changeset.state", "Closed"); 219 } 220 tfOpen.setText(msg); 221 222 if (cs.getUser() == null) { 223 msg = tr("anonymous"); 224 } else { 225 msg = cs.getUser().getName(); 226 } 227 tfUser.setText(msg); 228 DateFormat sdf = DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.SHORT); 229 230 tfCreatedOn.setText(cs.getCreatedAt() == null ? "" : sdf.format(cs.getCreatedAt())); 231 tfClosedOn.setText(cs.getClosedAt() == null ? "" : sdf.format(cs.getClosedAt())); 232 } 233 234 /** 235 * Constructs a new {@code ChangesetDetailPanel}. 236 */ 237 public ChangesetDetailPanel() { 238 build(); 239 } 240 241 protected void setCurrentChangeset(Changeset cs) { 242 current = cs; 243 if (cs == null) { 244 clearView(); 245 } else { 246 updateView(cs); 247 } 248 actDownloadChangesetContent.initProperties(current); 249 actUpdateChangesets.initProperties(current); 250 actRemoveFromCache.initProperties(current); 251 actSelectInCurrentLayer.updateEnabledState(); 252 actZoomInCurrentLayerAction.updateEnabledState(); 253 } 254 255 /* ---------------------------------------------------------------------------- */ 256 /* interface PropertyChangeListener */ 257 /* ---------------------------------------------------------------------------- */ 258 @Override 259 public void propertyChange(PropertyChangeEvent evt) { 260 if (! evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP)) 261 return; 262 setCurrentChangeset((Changeset)evt.getNewValue()); 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 DownloadChangesetContentAction extends AbstractAction { 292 public DownloadChangesetContentAction() { 293 putValue(NAME, tr("Download content")); 294 putValue(SMALL_ICON, ChangesetCacheManager.DOWNLOAD_CONTENT_ICON); 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, ChangesetCacheManager.DOWNLOAD_CONTENT_ICON); 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, ChangesetCacheManager.UPDATE_CONTENT_ICON); 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, ChangesetCacheManager.UPDATE_CONTENT_ICON); 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<>(); 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<>(); 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}