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