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