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    }