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.trn; 006 007import java.awt.BorderLayout; 008import java.awt.FlowLayout; 009import java.awt.event.ActionEvent; 010import java.awt.event.ComponentAdapter; 011import java.awt.event.ComponentEvent; 012import java.beans.PropertyChangeEvent; 013import java.beans.PropertyChangeListener; 014import java.util.Collection; 015import java.util.Collections; 016import java.util.List; 017import java.util.Objects; 018import java.util.Set; 019import java.util.stream.Collectors; 020 021import javax.swing.AbstractAction; 022import javax.swing.BorderFactory; 023import javax.swing.DefaultListSelectionModel; 024import javax.swing.JButton; 025import javax.swing.JComponent; 026import javax.swing.JOptionPane; 027import javax.swing.JPanel; 028import javax.swing.JPopupMenu; 029import javax.swing.JScrollPane; 030import javax.swing.JSeparator; 031import javax.swing.JTable; 032import javax.swing.JToolBar; 033import javax.swing.SwingConstants; 034import javax.swing.event.ListSelectionEvent; 035import javax.swing.event.ListSelectionListener; 036 037import org.openstreetmap.josm.actions.AbstractShowHistoryAction; 038import org.openstreetmap.josm.actions.AutoScaleAction; 039import org.openstreetmap.josm.actions.HistoryInfoAction; 040import org.openstreetmap.josm.actions.downloadtasks.ChangesetContentDownloadTask; 041import org.openstreetmap.josm.data.osm.Changeset; 042import org.openstreetmap.josm.data.osm.DataSet; 043import org.openstreetmap.josm.data.osm.OsmPrimitive; 044import org.openstreetmap.josm.data.osm.PrimitiveId; 045import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; 046import org.openstreetmap.josm.gui.HelpAwareOptionPane; 047import org.openstreetmap.josm.gui.MainApplication; 048import org.openstreetmap.josm.gui.help.HelpUtil; 049import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager; 050import org.openstreetmap.josm.gui.io.DownloadPrimitivesWithReferrersTask; 051import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 052import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 053import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 054import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 055import org.openstreetmap.josm.tools.ImageProvider; 056import org.openstreetmap.josm.tools.Utils; 057 058/** 059 * The panel which displays the content of a changeset in a scrollable table. 060 * 061 * It listens to property change events for {@link ChangesetCacheManagerModel#CHANGESET_IN_DETAIL_VIEW_PROP} 062 * and updates its view accordingly. 063 * @since 2689 064 */ 065public class ChangesetContentPanel extends JPanel implements PropertyChangeListener, ChangesetAware { 066 private JTable tblContent; 067 private ChangesetContentTableModel model; 068 private transient Changeset currentChangeset; 069 070 private DownloadChangesetContentAction actDownloadContentAction; 071 private ShowHistoryAction actShowHistory; 072 private SelectInCurrentLayerAction actSelectInCurrentLayerAction; 073 private ZoomInCurrentLayerAction actZoomInCurrentLayerAction; 074 075 private final HeaderPanel pnlHeader = new HeaderPanel(); 076 protected DownloadObjectAction actDownloadObjectAction; 077 078 protected void buildModels() { 079 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); 080 model = new ChangesetContentTableModel(selectionModel); 081 actDownloadContentAction = new DownloadChangesetContentAction(this); 082 actDownloadContentAction.initProperties(); 083 084 actDownloadObjectAction = new DownloadObjectAction(); 085 model.getSelectionModel().addListSelectionListener(actDownloadObjectAction); 086 087 actShowHistory = new ShowHistoryAction(); 088 model.getSelectionModel().addListSelectionListener(actShowHistory); 089 090 actSelectInCurrentLayerAction = new SelectInCurrentLayerAction(); 091 model.getSelectionModel().addListSelectionListener(actSelectInCurrentLayerAction); 092 MainApplication.getLayerManager().addActiveLayerChangeListener(actSelectInCurrentLayerAction); 093 094 actZoomInCurrentLayerAction = new ZoomInCurrentLayerAction(); 095 model.getSelectionModel().addListSelectionListener(actZoomInCurrentLayerAction); 096 MainApplication.getLayerManager().addActiveLayerChangeListener(actZoomInCurrentLayerAction); 097 098 addComponentListener( 099 new ComponentAdapter() { 100 @Override 101 public void componentShown(ComponentEvent e) { 102 MainApplication.getLayerManager().addAndFireActiveLayerChangeListener(actSelectInCurrentLayerAction); 103 MainApplication.getLayerManager().addAndFireActiveLayerChangeListener(actZoomInCurrentLayerAction); 104 } 105 106 @Override 107 public void componentHidden(ComponentEvent e) { 108 // make sure the listener is unregistered when the panel becomes invisible 109 MainApplication.getLayerManager().removeActiveLayerChangeListener(actSelectInCurrentLayerAction); 110 MainApplication.getLayerManager().removeActiveLayerChangeListener(actZoomInCurrentLayerAction); 111 } 112 } 113 ); 114 } 115 116 protected JPanel buildContentPanel() { 117 JPanel pnl = new JPanel(new BorderLayout()); 118 tblContent = new JTable( 119 model, 120 new ChangesetContentTableColumnModel(), 121 model.getSelectionModel() 122 ); 123 tblContent.setRowSorter(new ChangesetContentTableRowSorter(model)); 124 tblContent.addMouseListener(new PopupMenuLauncher(new ChangesetContentTablePopupMenu())); 125 HistoryInfoAction historyAction = MainApplication.getMenu().historyinfo; 126 tblContent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(historyAction.getShortcut().getKeyStroke(), "historyAction"); 127 tblContent.getActionMap().put("historyAction", historyAction); 128 tblContent.getTableHeader().setReorderingAllowed(false); 129 pnl.add(new JScrollPane(tblContent), BorderLayout.CENTER); 130 return pnl; 131 } 132 133 protected JPanel buildActionButtonPanel() { 134 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); 135 JToolBar tb = new JToolBar(SwingConstants.VERTICAL); 136 tb.setFloatable(false); 137 138 tb.add(actDownloadContentAction); 139 tb.addSeparator(); 140 tb.add(actDownloadObjectAction); 141 tb.add(actShowHistory); 142 tb.addSeparator(); 143 tb.add(actSelectInCurrentLayerAction); 144 tb.add(actZoomInCurrentLayerAction); 145 146 pnl.add(tb); 147 return pnl; 148 } 149 150 protected final void build() { 151 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 152 setLayout(new BorderLayout()); 153 buildModels(); 154 155 add(pnlHeader, BorderLayout.NORTH); 156 add(buildActionButtonPanel(), BorderLayout.WEST); 157 add(buildContentPanel(), BorderLayout.CENTER); 158 } 159 160 /** 161 * Constructs a new {@code ChangesetContentPanel}. 162 */ 163 public ChangesetContentPanel() { 164 build(); 165 } 166 167 /** 168 * Replies the changeset content model 169 * @return The model 170 */ 171 public ChangesetContentTableModel getModel() { 172 return model; 173 } 174 175 protected void setCurrentChangeset(Changeset cs) { 176 currentChangeset = cs; 177 if (cs == null) { 178 model.populate(null); 179 } else { 180 model.populate(cs.getContent()); 181 } 182 actDownloadContentAction.initProperties(); 183 pnlHeader.setChangeset(cs); 184 } 185 186 /* ---------------------------------------------------------------------------- */ 187 /* interface PropertyChangeListener */ 188 /* ---------------------------------------------------------------------------- */ 189 @Override 190 public void propertyChange(PropertyChangeEvent evt) { 191 if (!evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP)) 192 return; 193 Changeset cs = (Changeset) evt.getNewValue(); 194 setCurrentChangeset(cs); 195 } 196 197 private void alertNoPrimitivesTo(Collection<HistoryOsmPrimitive> primitives, String title, String helpTopic) { 198 HelpAwareOptionPane.showOptionDialog( 199 this, 200 trn("<html>The selected object is not available in the current<br>" 201 + "edit layer ''{0}''.</html>", 202 "<html>None of the selected objects is available in the current<br>" 203 + "edit layer ''{0}''.</html>", 204 primitives.size(), 205 Utils.escapeReservedCharactersHTML(MainApplication.getLayerManager().getEditLayer().getName()) 206 ), 207 title, JOptionPane.WARNING_MESSAGE, helpTopic 208 ); 209 } 210 211 private Set<HistoryOsmPrimitive> getSelectedPrimitives() { 212 return model.getSelectedPrimitives(tblContent); 213 } 214 215 class ChangesetContentTablePopupMenu extends JPopupMenu { 216 ChangesetContentTablePopupMenu() { 217 add(actDownloadContentAction); 218 add(new JSeparator()); 219 add(actDownloadObjectAction); 220 add(actShowHistory); 221 add(new JSeparator()); 222 add(actSelectInCurrentLayerAction); 223 add(actZoomInCurrentLayerAction); 224 } 225 } 226 227 class ShowHistoryAction extends AbstractShowHistoryAction implements ListSelectionListener { 228 229 ShowHistoryAction() { 230 super(); 231 updateEnabledState(); 232 } 233 234 protected final void updateEnabledState() { 235 setEnabled(model.hasSelectedPrimitives()); 236 } 237 238 @Override 239 public void actionPerformed(ActionEvent e) { 240 Set<HistoryOsmPrimitive> selected = getSelectedPrimitives(); 241 if (selected.isEmpty()) return; 242 HistoryBrowserDialogManager.getInstance().showHistory(selected); 243 } 244 245 @Override 246 public void valueChanged(ListSelectionEvent e) { 247 updateEnabledState(); 248 } 249 } 250 251 class DownloadObjectAction extends AbstractAction implements ListSelectionListener { 252 253 DownloadObjectAction() { 254 putValue(NAME, tr("Download objects")); 255 new ImageProvider("downloadprimitive").getResource().attachImageIcon(this, true); 256 putValue(SHORT_DESCRIPTION, tr("Download the current version of the selected objects")); 257 updateEnabledState(); 258 } 259 260 @Override 261 public void actionPerformed(ActionEvent e) { 262 final List<PrimitiveId> primitiveIds = getSelectedPrimitives().stream().map(HistoryOsmPrimitive::getPrimitiveId) 263 .collect(Collectors.toList()); 264 MainApplication.worker.submit(new DownloadPrimitivesWithReferrersTask(false, primitiveIds, true, true, null, null)); 265 } 266 267 protected final void updateEnabledState() { 268 setEnabled(model.hasSelectedPrimitives()); 269 } 270 271 @Override 272 public void valueChanged(ListSelectionEvent e) { 273 updateEnabledState(); 274 } 275 } 276 277 abstract class SelectionBasedAction extends AbstractAction implements ListSelectionListener, ActiveLayerChangeListener { 278 279 protected Set<OsmPrimitive> getTarget() { 280 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 281 if (isEnabled() && ds != null) { 282 return getSelectedPrimitives().stream() 283 .map(p -> ds.getPrimitiveById(p.getPrimitiveId())).filter(Objects::nonNull).collect(Collectors.toSet()); 284 } 285 return Collections.emptySet(); 286 } 287 288 public final void updateEnabledState() { 289 setEnabled(MainApplication.getLayerManager().getActiveDataSet() != null && model.hasSelectedPrimitives()); 290 } 291 292 @Override 293 public void valueChanged(ListSelectionEvent e) { 294 updateEnabledState(); 295 } 296 297 @Override 298 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 299 updateEnabledState(); 300 } 301 } 302 303 class SelectInCurrentLayerAction extends SelectionBasedAction { 304 305 SelectInCurrentLayerAction() { 306 putValue(NAME, tr("Select in layer")); 307 new ImageProvider("dialogs", "select").getResource().attachImageIcon(this); 308 putValue(SHORT_DESCRIPTION, tr("Select the corresponding primitives in the current data layer")); 309 updateEnabledState(); 310 } 311 312 @Override 313 public void actionPerformed(ActionEvent e) { 314 final Set<OsmPrimitive> target = getTarget(); 315 if (target.isEmpty()) { 316 alertNoPrimitivesTo(getSelectedPrimitives(), tr("Nothing to select"), 317 HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToSelectInLayer")); 318 return; 319 } 320 MainApplication.getLayerManager().getActiveDataSet().setSelected(target); 321 } 322 } 323 324 class ZoomInCurrentLayerAction extends SelectionBasedAction { 325 326 ZoomInCurrentLayerAction() { 327 putValue(NAME, tr("Zoom to in layer")); 328 new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this); 329 putValue(SHORT_DESCRIPTION, tr("Zoom to the corresponding objects in the current data layer")); 330 updateEnabledState(); 331 } 332 333 @Override 334 public void actionPerformed(ActionEvent e) { 335 final Set<OsmPrimitive> target = getTarget(); 336 if (target.isEmpty()) { 337 alertNoPrimitivesTo(getSelectedPrimitives(), tr("Nothing to zoom to"), 338 HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToZoomTo")); 339 return; 340 } 341 MainApplication.getLayerManager().getActiveDataSet().setSelected(target); 342 AutoScaleAction.zoomToSelection(); 343 } 344 } 345 346 private static class HeaderPanel extends JPanel { 347 348 private transient Changeset current; 349 350 HeaderPanel() { 351 build(); 352 } 353 354 protected final void build() { 355 setLayout(new FlowLayout(FlowLayout.LEFT)); 356 add(new JMultilineLabel(tr("The content of this changeset is not downloaded yet."))); 357 add(new JButton(new DownloadAction())); 358 359 } 360 361 public void setChangeset(Changeset cs) { 362 setVisible(cs != null && cs.getContent() == null); 363 this.current = cs; 364 } 365 366 private class DownloadAction extends AbstractAction { 367 DownloadAction() { 368 putValue(NAME, tr("Download now")); 369 putValue(SHORT_DESCRIPTION, tr("Download the changeset content")); 370 new ImageProvider("dialogs/changeset", "downloadchangesetcontent").getResource().attachImageIcon(this); 371 } 372 373 @Override 374 public void actionPerformed(ActionEvent evt) { 375 if (current == null) return; 376 ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(HeaderPanel.this, current.getId()); 377 ChangesetCacheManager.getInstance().runDownloadTask(task); 378 } 379 } 380 } 381 382 @Override 383 public Changeset getCurrentChangeset() { 384 return currentChangeset; 385 } 386}