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