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