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