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.trn;
006
007 import java.awt.BorderLayout;
008 import java.awt.FlowLayout;
009 import java.awt.event.ActionEvent;
010 import java.awt.event.ComponentAdapter;
011 import java.awt.event.ComponentEvent;
012 import java.awt.event.MouseAdapter;
013 import java.awt.event.MouseEvent;
014 import java.beans.PropertyChangeEvent;
015 import java.beans.PropertyChangeListener;
016 import java.util.ArrayList;
017 import java.util.Collection;
018 import java.util.HashSet;
019 import java.util.List;
020 import java.util.Set;
021
022 import javax.swing.AbstractAction;
023 import javax.swing.BorderFactory;
024 import javax.swing.DefaultListSelectionModel;
025 import javax.swing.JButton;
026 import javax.swing.JOptionPane;
027 import javax.swing.JPanel;
028 import javax.swing.JPopupMenu;
029 import javax.swing.JScrollPane;
030 import javax.swing.JSeparator;
031 import javax.swing.JTable;
032 import javax.swing.JToolBar;
033 import javax.swing.SwingUtilities;
034 import javax.swing.event.ListSelectionEvent;
035 import javax.swing.event.ListSelectionListener;
036
037 import org.openstreetmap.josm.Main;
038 import org.openstreetmap.josm.actions.AutoScaleAction;
039 import org.openstreetmap.josm.data.osm.Changeset;
040 import org.openstreetmap.josm.data.osm.OsmPrimitive;
041 import org.openstreetmap.josm.data.osm.history.History;
042 import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
043 import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
044 import org.openstreetmap.josm.gui.HelpAwareOptionPane;
045 import org.openstreetmap.josm.gui.JMultilineLabel;
046 import org.openstreetmap.josm.gui.MapView;
047 import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
048 import org.openstreetmap.josm.gui.help.HelpUtil;
049 import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager;
050 import org.openstreetmap.josm.gui.history.HistoryLoadTask;
051 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
052 import org.openstreetmap.josm.tools.BugReportExceptionHandler;
053 import org.openstreetmap.josm.tools.ImageProvider;
054
055 /**
056 * The panel which displays the content of a changeset in a scollable table.
057 *
058 * It listens to property change events for {@link ChangesetCacheManagerModel#CHANGESET_IN_DETAIL_VIEW_PROP}
059 * and updates its view accordingly.
060 *
061 */
062 public class ChangesetContentPanel extends JPanel implements PropertyChangeListener{
063
064 private ChangesetContentTableModel model;
065 private Changeset currentChangeset;
066 private JTable tblContent;
067
068 private DonwloadChangesetContentAction actDownloadContentAction;
069 private ShowHistoryAction actShowHistory;
070 private SelectInCurrentLayerAction actSelectInCurrentLayerAction;
071 private ZoomInCurrentLayerAction actZoomInCurrentLayerAction;
072
073 private HeaderPanel pnlHeader;
074
075 protected void buildModels() {
076 DefaultListSelectionModel selectionModel =new DefaultListSelectionModel();
077 model = new ChangesetContentTableModel(selectionModel);
078 actDownloadContentAction = new DonwloadChangesetContentAction();
079 actDownloadContentAction.initProperties(currentChangeset);
080 actShowHistory = new ShowHistoryAction();
081 model.getSelectionModel().addListSelectionListener(actShowHistory);
082
083 actSelectInCurrentLayerAction = new SelectInCurrentLayerAction();
084 model.getSelectionModel().addListSelectionListener(actSelectInCurrentLayerAction);
085 MapView.addEditLayerChangeListener(actSelectInCurrentLayerAction);
086
087 actZoomInCurrentLayerAction = new ZoomInCurrentLayerAction();
088 model.getSelectionModel().addListSelectionListener(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(actSelectInCurrentLayerAction);
098 MapView.removeEditLayerChangeListener(actZoomInCurrentLayerAction);
099 }
100 }
101 );
102 }
103
104 protected JPanel buildContentPanel() {
105 JPanel pnl = new JPanel(new BorderLayout());
106 tblContent = new JTable(
107 model,
108 new ChangesetContentTableColumnModel(),
109 model.getSelectionModel()
110 );
111 tblContent.addMouseListener(new ChangesetContentTablePopupMenuLauncher());
112 pnl.add(new JScrollPane(tblContent), BorderLayout.CENTER);
113 return pnl;
114 }
115
116 protected JPanel buildActionButtonPanel() {
117 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
118 JToolBar tb = new JToolBar(JToolBar.VERTICAL);
119 tb.setFloatable(false);
120
121 tb.add(actDownloadContentAction);
122 tb.add(actShowHistory);
123 tb.add(actSelectInCurrentLayerAction);
124 tb.add(actZoomInCurrentLayerAction);
125
126 pnl.add(tb);
127 return pnl;
128 }
129
130 protected void build() {
131 setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
132 setLayout(new BorderLayout());
133 buildModels();
134
135 add(pnlHeader = new HeaderPanel(), BorderLayout.NORTH);
136 add(buildActionButtonPanel(), BorderLayout.WEST);
137 add(buildContentPanel(), BorderLayout.CENTER);
138
139 }
140
141 public ChangesetContentPanel() {
142 build();
143 }
144
145 public ChangesetContentTableModel getModel() {
146 return model;
147 }
148
149 protected void setCurrentChangeset(Changeset cs) {
150 currentChangeset = cs;
151 if (cs == null) {
152 model.populate(null);
153 } else {
154 model.populate(cs.getContent());
155 }
156 actDownloadContentAction.initProperties(cs);
157 pnlHeader.setChangeset(cs);
158 }
159
160 /* ---------------------------------------------------------------------------- */
161 /* interface PropertyChangeListener */
162 /* ---------------------------------------------------------------------------- */
163 public void propertyChange(PropertyChangeEvent evt) {
164 if(!evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP))
165 return;
166 Changeset cs = (Changeset)evt.getNewValue();
167 setCurrentChangeset(cs);
168 }
169
170 /**
171 * Downloads/Updates the content of the changeset
172 *
173 */
174 class DonwloadChangesetContentAction extends AbstractAction{
175 public DonwloadChangesetContentAction() {
176 putValue(NAME, tr("Download content"));
177 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent"));
178 putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server"));
179 }
180
181 public void actionPerformed(ActionEvent evt) {
182 if (currentChangeset == null) return;
183 ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetContentPanel.this,currentChangeset.getId());
184 ChangesetCacheManager.getInstance().runDownloadTask(task);
185 }
186
187 public void initProperties(Changeset cs) {
188 if (cs == null) {
189 setEnabled(false);
190 return;
191 } else {
192 setEnabled(true);
193 }
194 if (cs.getContent() == null) {
195 putValue(NAME, tr("Download content"));
196 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent"));
197 putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server"));
198 } else {
199 putValue(NAME, tr("Update content"));
200 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","updatechangesetcontent"));
201 putValue(SHORT_DESCRIPTION, tr("Update the changeset content from the OSM server"));
202 }
203 }
204 }
205
206 class ChangesetContentTablePopupMenuLauncher extends MouseAdapter {
207 ChangesetContentTablePopupMenu menu = new ChangesetContentTablePopupMenu();
208
209 protected void launch(MouseEvent evt) {
210 if (! evt.isPopupTrigger()) return;
211 if (! model.hasSelectedPrimitives()) {
212 int row = tblContent.rowAtPoint(evt.getPoint());
213 if (row >= 0) {
214 model.setSelectedByIdx(row);
215 }
216 }
217 menu.show(tblContent, evt.getPoint().x, evt.getPoint().y);
218 }
219
220 @Override
221 public void mouseClicked(MouseEvent evt) {
222 launch(evt);
223 }
224
225 @Override
226 public void mousePressed(MouseEvent evt) {
227 launch(evt);
228 }
229
230 @Override
231 public void mouseReleased(MouseEvent evt) {
232 launch(evt);
233 }
234 }
235
236 class ChangesetContentTablePopupMenu extends JPopupMenu {
237 public ChangesetContentTablePopupMenu() {
238 add(actDownloadContentAction);
239 add(actShowHistory);
240 add(new JSeparator());
241 add(actSelectInCurrentLayerAction);
242 add(actZoomInCurrentLayerAction);
243 }
244 }
245
246 class ShowHistoryAction extends AbstractAction implements ListSelectionListener{
247 public ShowHistoryAction() {
248 putValue(NAME, tr("Show history"));
249 putValue(SMALL_ICON, ImageProvider.get("dialogs", "history"));
250 putValue(SHORT_DESCRIPTION, tr("Download and show the history of the selected objects"));
251 updateEnabledState();
252 }
253
254 protected List<HistoryOsmPrimitive> filterPrimitivesWithUnloadedHistory(Collection<HistoryOsmPrimitive> primitives) {
255 ArrayList<HistoryOsmPrimitive> ret = new ArrayList<HistoryOsmPrimitive>(primitives.size());
256 for (HistoryOsmPrimitive p: primitives) {
257 if (HistoryDataSet.getInstance().getHistory(p.getPrimitiveId()) == null) {
258 ret.add(p);
259 }
260 }
261 return ret;
262 }
263
264 public void showHistory(final Collection<HistoryOsmPrimitive> primitives) {
265
266 List<HistoryOsmPrimitive> toLoad = filterPrimitivesWithUnloadedHistory(primitives);
267 if (!toLoad.isEmpty()) {
268 HistoryLoadTask task = new HistoryLoadTask(ChangesetContentPanel.this);
269 for (HistoryOsmPrimitive p: toLoad) {
270 task.add(p);
271 }
272 Main.worker.submit(task);
273 }
274
275 Runnable r = new Runnable() {
276 public void run() {
277 try {
278 for (HistoryOsmPrimitive p : primitives) {
279 History h = HistoryDataSet.getInstance().getHistory(p.getPrimitiveId());
280 if (h == null) {
281 continue;
282 }
283 HistoryBrowserDialogManager.getInstance().show(h);
284 }
285 } catch (final Exception e) {
286 SwingUtilities.invokeLater(new Runnable() {
287 public void run() {
288 BugReportExceptionHandler.handleException(e);
289 }
290 });
291 }
292
293 }
294 };
295 Main.worker.submit(r);
296 }
297
298 protected void updateEnabledState() {
299 setEnabled(model.hasSelectedPrimitives());
300 }
301
302 public void actionPerformed(ActionEvent arg0) {
303 Set<HistoryOsmPrimitive> selected = model.getSelectedPrimitives();
304 if (selected.isEmpty()) return;
305 showHistory(selected);
306 }
307
308 public void valueChanged(ListSelectionEvent e) {
309 updateEnabledState();
310 }
311 }
312
313 class SelectInCurrentLayerAction extends AbstractAction implements ListSelectionListener, EditLayerChangeListener{
314
315 public SelectInCurrentLayerAction() {
316 putValue(NAME, tr("Select in layer"));
317 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
318 putValue(SHORT_DESCRIPTION, tr("Select the corresponding primitives in the current data layer"));
319 updateEnabledState();
320 }
321
322 protected void alertNoPrimitivesToSelect(Collection<HistoryOsmPrimitive> primitives) {
323 HelpAwareOptionPane.showOptionDialog(
324 ChangesetContentPanel.this,
325 trn("<html>The selected object is not available in the current<br>"
326 + "edit layer ''{0}''.</html>",
327 "<html>None of the selected objects is available in the current<br>"
328 + "edit layer ''{0}''.</html>",
329 primitives.size(),
330 Main.main.getEditLayer().getName()
331 ),
332 tr("Nothing to select"),
333 JOptionPane.WARNING_MESSAGE,
334 HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToSelectInLayer")
335 );
336 }
337
338 public void actionPerformed(ActionEvent arg0) {
339 if (!isEnabled())
340 return;
341 if (Main.main == null || Main.main.getEditLayer() == null) return;
342 OsmDataLayer layer = Main.main.getEditLayer();
343 Set<HistoryOsmPrimitive> selected = model.getSelectedPrimitives();
344 Set<OsmPrimitive> target = new HashSet<OsmPrimitive>();
345 for (HistoryOsmPrimitive p : model.getSelectedPrimitives()) {
346 OsmPrimitive op = layer.data.getPrimitiveById(p.getPrimitiveId());
347 if (op != null) {
348 target.add(op);
349 }
350 }
351 if (target.isEmpty()) {
352 alertNoPrimitivesToSelect(selected);
353 return;
354 }
355 layer.data.setSelected(target);
356 }
357
358 public void updateEnabledState() {
359 if (Main.main == null || Main.main.getEditLayer() == null){
360 setEnabled(false);
361 return;
362 }
363 setEnabled(model.hasSelectedPrimitives());
364 }
365
366 public void valueChanged(ListSelectionEvent e) {
367 updateEnabledState();
368 }
369
370 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
371 updateEnabledState();
372 }
373 }
374
375 class ZoomInCurrentLayerAction extends AbstractAction implements ListSelectionListener, EditLayerChangeListener{
376
377 public ZoomInCurrentLayerAction() {
378 putValue(NAME, tr("Zoom to in layer"));
379 putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection"));
380 putValue(SHORT_DESCRIPTION, tr("Zoom to the corresponding objects in the current data layer"));
381 updateEnabledState();
382 }
383
384 protected void alertNoPrimitivesToZoomTo(Collection<HistoryOsmPrimitive> primitives) {
385 HelpAwareOptionPane.showOptionDialog(
386 ChangesetContentPanel.this,
387 trn("<html>The selected object is not available in the current<br>"
388 + "edit layer ''{0}''.</html>",
389 "<html>None of the selected objects is available in the current<br>"
390 + "edit layer ''{0}''.</html>",
391 primitives.size(),
392 Main.main.getEditLayer().getName()
393 ),
394 tr("Nothing to zoom to"),
395 JOptionPane.WARNING_MESSAGE,
396 HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToZoomTo")
397 );
398 }
399
400 public void actionPerformed(ActionEvent arg0) {
401 if (!isEnabled())
402 return;
403 if (Main.main == null || Main.main.getEditLayer() == null) return;
404 OsmDataLayer layer = Main.main.getEditLayer();
405 Set<HistoryOsmPrimitive> selected = model.getSelectedPrimitives();
406 Set<OsmPrimitive> target = new HashSet<OsmPrimitive>();
407 for (HistoryOsmPrimitive p : model.getSelectedPrimitives()) {
408 OsmPrimitive op = layer.data.getPrimitiveById(p.getPrimitiveId());
409 if (op != null) {
410 target.add(op);
411 }
412 }
413 if (target.isEmpty()) {
414 alertNoPrimitivesToZoomTo(selected);
415 return;
416 }
417 layer.data.setSelected(target);
418 AutoScaleAction.zoomToSelection();
419 }
420
421 public void updateEnabledState() {
422 if (Main.main == null || Main.main.getEditLayer() == null){
423 setEnabled(false);
424 return;
425 }
426 setEnabled(model.hasSelectedPrimitives());
427 }
428
429 public void valueChanged(ListSelectionEvent e) {
430 updateEnabledState();
431 }
432
433 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
434 updateEnabledState();
435 }
436 }
437
438 static private class HeaderPanel extends JPanel {
439
440 private JMultilineLabel lblMessage;
441 private Changeset current;
442
443 protected void build() {
444 setLayout(new FlowLayout(FlowLayout.LEFT));
445 lblMessage = new JMultilineLabel(
446 tr("The content of this changeset is not downloaded yet.")
447 );
448 add(lblMessage);
449 add(new JButton(new DownloadAction()));
450
451 }
452
453 public HeaderPanel() {
454 build();
455 }
456
457 public void setChangeset(Changeset cs) {
458 setVisible(cs != null && cs.getContent() == null);
459 this.current = cs;
460 }
461
462 private class DownloadAction extends AbstractAction {
463 public DownloadAction() {
464 putValue(NAME, tr("Download now"));
465 putValue(SHORT_DESCRIPTION, tr("Download the changeset content"));
466 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangesetcontent"));
467 }
468
469 public void actionPerformed(ActionEvent evt) {
470 if (current == null) return;
471 ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(HeaderPanel.this, current.getId());
472 ChangesetCacheManager.getInstance().runDownloadTask(task);
473 }
474 }
475 }
476 }