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    }