// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.dialogs.changeset; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.DefaultListSelectionModel; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.osm.Changeset; import org.openstreetmap.josm.data.osm.ChangesetCache; import org.openstreetmap.josm.gui.HelpAwareOptionPane; import org.openstreetmap.josm.gui.JosmUserIdentityManager; import org.openstreetmap.josm.gui.SideButton; import org.openstreetmap.josm.gui.dialogs.changeset.query.ChangesetQueryDialog; import org.openstreetmap.josm.gui.dialogs.changeset.query.ChangesetQueryTask; import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; import org.openstreetmap.josm.gui.help.HelpUtil; import org.openstreetmap.josm.gui.io.CloseChangesetTask; import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; import org.openstreetmap.josm.io.ChangesetQuery; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.WindowGeometry; /** * ChangesetCacheManager manages the local cache of changesets * retrieved from the OSM API. It displays both a table of the locally cached changesets * and detail information about an individual changeset. It also provides actions for * downloading, querying, closing changesets, in addition to removing changesets from * the local cache. * */ public class ChangesetCacheManager extends JFrame { /** the unique instance of the cache manager */ private static ChangesetCacheManager instance; /** * Replies the unique instance of the changeset cache manager * * @return the unique instance of the changeset cache manager */ public static ChangesetCacheManager getInstance() { if (instance == null) { instance = new ChangesetCacheManager(); } return instance; } /** * Hides and destroys the unique instance of the changeset cache * manager. * */ public static void destroyInstance() { if (instance != null) { instance.setVisible(true); instance.dispose(); instance = null; } } private ChangesetCacheManagerModel model; private JSplitPane spContent; private boolean needsSplitPaneAdjustment; private RemoveFromCacheAction actRemoveFromCacheAction; private CloseSelectedChangesetsAction actCloseSelectedChangesetsAction; private DownloadSelectedChangesetsAction actDownloadSelectedChangesets; private DownloadSelectedChangesetContentAction actDownloadSelectedContent; private JTable tblChangesets; /** * Creates the various models required */ protected void buildModel() { DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); model = new ChangesetCacheManagerModel(selectionModel); actRemoveFromCacheAction = new RemoveFromCacheAction(); actCloseSelectedChangesetsAction = new CloseSelectedChangesetsAction(); actDownloadSelectedChangesets = new DownloadSelectedChangesetsAction(); actDownloadSelectedContent = new DownloadSelectedChangesetContentAction(); } /** * builds the toolbar panel in the heading of the dialog * * @return the toolbar panel */ protected JPanel buildToolbarPanel() { JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); SideButton btn = new SideButton(new QueryAction()); pnl.add(btn); pnl.add(new SingleChangesetDownloadPanel()); pnl.add(new SideButton(new DownloadMyChangesets())); return pnl; } /** * builds the button panel in the footer of the dialog * * @return the button row pane */ protected JPanel buildButtonPanel() { JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER)); //-- cancel and close action pnl.add(new SideButton(new CancelAction())); //-- help action pnl.add(new SideButton( new ContextSensitiveHelpAction( HelpUtil.ht("/Dialog/ChangesetCacheManager")) ) ); return pnl; } /** * Builds the panel with the changeset details * * @return the panel with the changeset details */ protected JPanel buildChangesetDetailPanel() { JPanel pnl = new JPanel(new BorderLayout()); JTabbedPane tp = new JTabbedPane(); // -- add the details panel ChangesetDetailPanel pnlChangesetDetail; tp.add(pnlChangesetDetail = new ChangesetDetailPanel()); model.addPropertyChangeListener(pnlChangesetDetail); // -- add the tags panel ChangesetTagsPanel pnlChangesetTags = new ChangesetTagsPanel(); tp.add(pnlChangesetTags); model.addPropertyChangeListener(pnlChangesetTags); // -- add the panel for the changeset content ChangesetContentPanel pnlChangesetContent = new ChangesetContentPanel(); tp.add(pnlChangesetContent); model.addPropertyChangeListener(pnlChangesetContent); tp.setTitleAt(0, tr("Properties")); tp.setToolTipTextAt(0, tr("Display the basic properties of the changeset")); tp.setTitleAt(1, tr("Tags")); tp.setToolTipTextAt(1, tr("Display the tags of the changeset")); tp.setTitleAt(2, tr("Content")); tp.setToolTipTextAt(2, tr("Display the objects created, updated, and deleted by the changeset")); pnl.add(tp, BorderLayout.CENTER); return pnl; } /** * builds the content panel of the dialog * * @return the content panel */ protected JPanel buildContentPanel() { JPanel pnl = new JPanel(new BorderLayout()); spContent = new JSplitPane(JSplitPane.VERTICAL_SPLIT); spContent.setLeftComponent(buildChangesetTablePanel()); spContent.setRightComponent(buildChangesetDetailPanel()); spContent.setOneTouchExpandable(true); spContent.setDividerLocation(0.5); pnl.add(spContent, BorderLayout.CENTER); return pnl; } /** * Builds the table with actions which can be applied to the currently visible changesets * in the changeset table. * * @return */ protected JPanel buildChangesetTableActionPanel() { JPanel pnl = new JPanel(new BorderLayout()); JToolBar tb = new JToolBar(JToolBar.VERTICAL); tb.setFloatable(false); // -- remove from cache action model.getSelectionModel().addListSelectionListener(actRemoveFromCacheAction); tb.add(actRemoveFromCacheAction); // -- close selected changesets action model.getSelectionModel().addListSelectionListener(actCloseSelectedChangesetsAction); tb.add(actCloseSelectedChangesetsAction); // -- download selected changesets model.getSelectionModel().addListSelectionListener(actDownloadSelectedChangesets); tb.add(actDownloadSelectedChangesets); // -- download the content of the selected changesets model.getSelectionModel().addListSelectionListener(actDownloadSelectedContent); tb.add(actDownloadSelectedContent); pnl.add(tb, BorderLayout.CENTER); return pnl; } /** * Builds the panel with the table of changesets * * @return the panel with the table of changesets */ protected JPanel buildChangesetTablePanel() { JPanel pnl = new JPanel(new BorderLayout()); tblChangesets = new JTable( model, new ChangesetCacheTableColumnModel(), model.getSelectionModel() ); tblChangesets.addMouseListener(new ChangesetTablePopupMenuLauncher()); tblChangesets.addMouseListener(new DblClickHandler()); tblChangesets.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "showDetails"); tblChangesets.getActionMap().put("showDetails", new ShowDetailAction()); model.getSelectionModel().addListSelectionListener(new ChangesetDetailViewSynchronizer()); // activate DEL on the table tblChangesets.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "removeFromCache"); tblChangesets.getActionMap().put("removeFromCache", actRemoveFromCacheAction); pnl.add(new JScrollPane(tblChangesets), BorderLayout.CENTER); pnl.add(buildChangesetTableActionPanel(), BorderLayout.WEST); return pnl; } protected void build() { setTitle(tr("Changeset Management Dialog")); setIconImage(ImageProvider.get("dialogs/changeset", "changesetmanager").getImage()); Container cp = getContentPane(); cp.setLayout(new BorderLayout()); buildModel(); cp.add(buildToolbarPanel(), BorderLayout.NORTH); cp.add(buildContentPanel(), BorderLayout.CENTER); cp.add(buildButtonPanel(), BorderLayout.SOUTH); // the help context HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/ChangesetCacheManager")); // make the dialog respond to ESC getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "cancelAndClose"); getRootPane().getActionMap().put("cancelAndClose", new CancelAction()); // install a window event handler addWindowListener(new WindowEventHandler()); } public ChangesetCacheManager() { build(); } @Override public void setVisible(boolean visible) { if (visible) { new WindowGeometry( getClass().getName() + ".geometry", WindowGeometry.centerInWindow( getParent(), new Dimension(1000,600) ) ).applySafe(this); needsSplitPaneAdjustment = true; model.init(); } else if (!visible && isShowing()){ model.tearDown(); new WindowGeometry(this).remember(getClass().getName() + ".geometry"); } super.setVisible(visible); } /** * Handler for window events * */ class WindowEventHandler extends WindowAdapter { @Override public void windowClosing(WindowEvent e) { new CancelAction().cancelAndClose(); } @Override public void windowActivated(WindowEvent arg0) { if (needsSplitPaneAdjustment) { spContent.setDividerLocation(0.5); needsSplitPaneAdjustment = false; } } } /** * the cancel / close action */ static class CancelAction extends AbstractAction { public CancelAction() { putValue(NAME, tr("Close")); putValue(SMALL_ICON, ImageProvider.get("cancel")); putValue(SHORT_DESCRIPTION, tr("Close the dialog")); } public void cancelAndClose() { destroyInstance(); } public void actionPerformed(ActionEvent arg0) { cancelAndClose(); } } /** * The action to query and download changesets */ class QueryAction extends AbstractAction { public QueryAction() { putValue(NAME, tr("Query")); putValue(SMALL_ICON, ImageProvider.get("dialogs","search")); putValue(SHORT_DESCRIPTION, tr("Launch the dialog for querying changesets")); } public void actionPerformed(ActionEvent evt) { ChangesetQueryDialog dialog = new ChangesetQueryDialog(ChangesetCacheManager.this); dialog.initForUserInput(); dialog.setVisible(true); if (dialog.isCanceled()) return; ChangesetQuery query = dialog.getChangesetQuery(); if (query == null) return; ChangesetQueryTask task = new ChangesetQueryTask(ChangesetCacheManager.this, query); ChangesetCacheManager.getInstance().runDownloadTask(task); } } /** * Removes the selected changesets from the local changeset cache * */ class RemoveFromCacheAction extends AbstractAction implements ListSelectionListener{ public RemoveFromCacheAction() { putValue(NAME, tr("Remove from cache")); putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete")); putValue(SHORT_DESCRIPTION, tr("Remove the selected changesets from the local cache")); updateEnabledState(); } public void actionPerformed(ActionEvent arg0) { List<Changeset> selected = model.getSelectedChangesets(); ChangesetCache.getInstance().remove(selected); } protected void updateEnabledState() { setEnabled(model.hasSelectedChangesets()); } public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } } /** * Closes the selected changesets * */ class CloseSelectedChangesetsAction extends AbstractAction implements ListSelectionListener{ public CloseSelectedChangesetsAction() { putValue(NAME, tr("Close")); putValue(SMALL_ICON, ImageProvider.get("closechangeset")); putValue(SHORT_DESCRIPTION, tr("Close the selected changesets")); updateEnabledState(); } public void actionPerformed(ActionEvent arg0) { List<Changeset> selected = model.getSelectedChangesets(); Main.worker.submit(new CloseChangesetTask(selected)); } protected void updateEnabledState() { List<Changeset> selected = model.getSelectedChangesets(); JosmUserIdentityManager im = JosmUserIdentityManager.getInstance(); for (Changeset cs: selected) { if (cs.isOpen()) { if (im.isPartiallyIdentified() && cs.getUser() != null && cs.getUser().getName().equals(im.getUserName())) { setEnabled(true); return; } if (im.isFullyIdentified() && cs.getUser() != null && cs.getUser().getId() == im.getUserId()) { setEnabled(true); return; } } } setEnabled(false); } public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } } /** * Downloads the selected changesets * */ class DownloadSelectedChangesetsAction extends AbstractAction implements ListSelectionListener{ public DownloadSelectedChangesetsAction() { putValue(NAME, tr("Update changeset")); putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "updatechangeset")); putValue(SHORT_DESCRIPTION, tr("Updates the selected changesets with current data from the OSM server")); updateEnabledState(); } public void actionPerformed(ActionEvent arg0) { List<Changeset> selected = model.getSelectedChangesets(); ChangesetHeaderDownloadTask task =ChangesetHeaderDownloadTask.buildTaskForChangesets(ChangesetCacheManager.this,selected); ChangesetCacheManager.getInstance().runDownloadTask(task); } protected void updateEnabledState() { setEnabled(model.hasSelectedChangesets()); } public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } } /** * Downloads the content of selected changesets from the OSM server * */ class DownloadSelectedChangesetContentAction extends AbstractAction implements ListSelectionListener{ public DownloadSelectedChangesetContentAction() { putValue(NAME, tr("Download changeset content")); putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangesetcontent")); putValue(SHORT_DESCRIPTION, tr("Download the content of the selected changesets from the server")); updateEnabledState(); } public void actionPerformed(ActionEvent arg0) { ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetCacheManager.this,model.getSelectedChangesetIds()); ChangesetCacheManager.getInstance().runDownloadTask(task); } protected void updateEnabledState() { setEnabled(model.hasSelectedChangesets()); } public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } } class ShowDetailAction extends AbstractAction { public void showDetails() { List<Changeset> selected = model.getSelectedChangesets(); if (selected.size() != 1) return; model.setChangesetInDetailView(selected.get(0)); } public void actionPerformed(ActionEvent arg0) { showDetails(); } } class DownloadMyChangesets extends AbstractAction { public DownloadMyChangesets() { putValue(NAME, tr("My changesets")); putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangeset")); putValue(SHORT_DESCRIPTION, tr("Download my changesets from the OSM server (max. 100 changesets)")); } protected void alertAnonymousUser() { HelpAwareOptionPane.showOptionDialog( ChangesetCacheManager.this, tr("<html>JOSM is currently running with an anonymous user. It cannot download<br>" + "your changesets from the OSM server unless you enter your OSM user name<br>" + "in the JOSM preferences.</html>" ), tr("Warning"), JOptionPane.WARNING_MESSAGE, HelpUtil.ht("/Dialog/ChangesetCacheManager#CanDownloadMyChangesets") ); } public void actionPerformed(ActionEvent arg0) { JosmUserIdentityManager im = JosmUserIdentityManager.getInstance(); im.initFromPreferences(); if (im.isAnonymous()) { alertAnonymousUser(); return; } ChangesetQuery query = new ChangesetQuery(); if (im.isFullyIdentified()) { query = query.forUser(im.getUserId()); } else { query = query.forUser(im.getUserName()); } ChangesetQueryTask task = new ChangesetQueryTask(ChangesetCacheManager.this, query); ChangesetCacheManager.getInstance().runDownloadTask(task); } } class DblClickHandler extends MouseAdapter { @Override public void mouseClicked(MouseEvent evt) { if (! SwingUtilities.isLeftMouseButton(evt) || evt.getClickCount()<2) return; new ShowDetailAction().showDetails(); } } class ChangesetTablePopupMenuLauncher extends PopupMenuLauncher { ChangesetTablePopupMenu menu = new ChangesetTablePopupMenu(); @Override public void launch(MouseEvent evt) { if (! model.hasSelectedChangesets()) { int row = tblChangesets.rowAtPoint(evt.getPoint()); if (row >= 0) { model.setSelectedByIdx(row); } } menu.show(tblChangesets, evt.getPoint().x, evt.getPoint().y); } } class ChangesetTablePopupMenu extends JPopupMenu { public ChangesetTablePopupMenu() { add(actRemoveFromCacheAction); add(actCloseSelectedChangesetsAction); add(actDownloadSelectedChangesets); add(actDownloadSelectedContent); } } class ChangesetDetailViewSynchronizer implements ListSelectionListener { public void valueChanged(ListSelectionEvent e) { List<Changeset> selected = model.getSelectedChangesets(); if (selected.size() == 1) { model.setChangesetInDetailView(selected.get(0)); } else { model.setChangesetInDetailView(null); } } } /** * Selects the changesets in <code>changests</code>, provided the * respective changesets are already present in the local changeset cache. * * @param ids the collection of changesets. If null, the selection is cleared. */ public void setSelectedChangesets(Collection<Changeset> changesets) { model.setSelectedChangesets(changesets); int idx = model.getSelectionModel().getMinSelectionIndex(); if (idx < 0) return; tblChangesets.scrollRectToVisible(tblChangesets.getCellRect(idx, 0, true)); repaint(); } /** * Selects the changesets with the ids in <code>ids</code>, provided the * respective changesets are already present in the local changeset cache. * * @param ids the collection of ids. If null, the selection is cleared. */ public void setSelectedChangesetsById(Collection<Integer> ids) { if (ids == null) { setSelectedChangesets(null); return; } Set<Changeset> toSelect = new HashSet<Changeset>(); ChangesetCache cc = ChangesetCache.getInstance(); for (int id: ids) { if (cc.contains(id)) { toSelect.add(cc.get(id)); } } setSelectedChangesets(toSelect); } public void runDownloadTask(final ChangesetDownloadTask task) { Main.worker.submit(task); Runnable r = new Runnable() { public void run() { if (task.isCanceled() || task.isFailed()) return; setSelectedChangesets(task.getDownloadedChangesets()); } }; Main.worker.submit(r); } }