001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.plugins.streetside.gui; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.BorderLayout; 007import java.awt.Component; 008import java.awt.Container; 009import java.awt.Dimension; 010import java.awt.GridBagConstraints; 011import java.awt.GridBagLayout; 012import java.awt.event.KeyEvent; 013import java.util.Collections; 014import java.util.Objects; 015 016import javax.swing.Box; 017import javax.swing.JPanel; 018import javax.swing.JProgressBar; 019import javax.swing.JScrollPane; 020import javax.swing.JSeparator; 021import javax.swing.JTree; 022import javax.swing.SwingUtilities; 023import javax.swing.tree.DefaultMutableTreeNode; 024import javax.swing.tree.DefaultTreeModel; 025import javax.swing.tree.TreeSelectionModel; 026 027import org.openstreetmap.josm.gui.SideButton; 028import org.openstreetmap.josm.gui.dialogs.ToggleDialog; 029import org.openstreetmap.josm.plugins.streetside.StreetsideLayer; 030import org.openstreetmap.josm.plugins.streetside.StreetsideLocationChangeset; 031import org.openstreetmap.josm.plugins.streetside.history.StreetsideRecord; 032import org.openstreetmap.josm.plugins.streetside.history.commands.StreetsideCommand; 033import org.openstreetmap.josm.plugins.streetside.utils.StreetsideChangesetListener; 034import org.openstreetmap.josm.tools.GBC; 035import org.openstreetmap.josm.tools.Shortcut; 036 037import org.openstreetmap.josm.plugins.streetside.actions.StreetsideSubmitCurrentChangesetAction; 038 039/** 040 * Toggle dialog that shows you the latest {@link StreetsideCommand} done and 041 * allows the user to revert them. 042 * 043 * @see StreetsideRecord 044 * @see StreetsideCommand 045 */ 046public final class StreetsideChangesetDialog extends ToggleDialog implements StreetsideChangesetListener { 047 private static final long serialVersionUID = -3019715241209349372L; 048 private static StreetsideChangesetDialog instance; 049 050 private final DefaultTreeModel changesetTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode()); 051 052 private final Component spacer = Box.createRigidArea(new Dimension(0, 3)); 053 054 private final Container rootComponent = new JPanel(new BorderLayout()); 055 private final SideButton submitButton = new SideButton(new StreetsideSubmitCurrentChangesetAction(this)); 056 private final JProgressBar uploadPendingProgress = new JProgressBar(); 057 058 /** 059 * Destroys the unique instance of the class. 060 */ 061 public static void destroyInstance() { 062 StreetsideChangesetDialog.instance = null; 063 } 064 065 private StreetsideChangesetDialog() { 066 super( 067 tr("Current Streetside changeset"), 068 "streetside-upload", 069 tr("Open Streetside changeset dialog"), 070 Shortcut.registerShortcut( 071 tr("Streetside changeset"), tr("Open Streetside changeset dialog"), KeyEvent.VK_9, Shortcut.NONE 072 ), 073 200 074 ); 075 createLayout(rootComponent, false, Collections.singletonList(submitButton)); 076 077 final JTree changesetTree = new JTree(changesetTreeModel); 078 changesetTree.expandRow(0); 079 changesetTree.setShowsRootHandles(true); 080 changesetTree.setRootVisible(false); 081 changesetTree.setCellRenderer(new StreetsideImageTreeCellRenderer()); 082 changesetTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 083 084 final JPanel treesPanel = new JPanel(new GridBagLayout()); 085 treesPanel.add(spacer, GBC.eol()); 086 treesPanel.add(changesetTree, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); 087 treesPanel.add(new JSeparator(), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); 088 treesPanel.add(Box.createRigidArea(new Dimension(0, 0)), GBC.std().weight(0, 1)); 089 rootComponent.add(new JScrollPane(treesPanel), BorderLayout.CENTER); 090 091 uploadPendingProgress.setIndeterminate(true); 092 uploadPendingProgress.setString(tr("Submitting changeset to server…")); 093 uploadPendingProgress.setStringPainted(true); 094 095 setUploadPending(false); 096 } 097 098 /** 099 * Returns the unique instance of the class. 100 * 101 * @return The unique instance of the class. 102 */ 103 public static synchronized StreetsideChangesetDialog getInstance() { 104 if (instance == null) { 105 instance = new StreetsideChangesetDialog(); 106 } 107 return instance; 108 } 109 110 private void buildTree() { 111 final StreetsideLocationChangeset changeset = StreetsideLayer.getInstance().getLocationChangeset(); 112 submitButton.setEnabled(!changeset.isEmpty()); 113 DefaultMutableTreeNode changesetRoot = new DefaultMutableTreeNode(); 114 115 changeset.parallelStream().filter(Objects::nonNull).forEach(img -> { 116 final DefaultMutableTreeNode node = new DefaultMutableTreeNode(img); 117 changesetRoot.add(node); 118 }); 119 120 spacer.setVisible(changeset.isEmpty()); 121 122 changesetTreeModel.setRoot(changesetRoot); 123 } 124 125 public void setUploadPending(final boolean isUploadPending) { 126 if (isUploadPending) { 127 rootComponent.add(uploadPendingProgress, BorderLayout.SOUTH); 128 } else { 129 rootComponent.remove(uploadPendingProgress); 130 } 131 submitButton.setEnabled(!isUploadPending && StreetsideLayer.hasInstance() && !StreetsideLayer.getInstance().getLocationChangeset().isEmpty()); 132 rootComponent.revalidate(); 133 rootComponent.repaint(); 134 } 135 136 @Override 137 public void changesetChanged() { 138 if (!SwingUtilities.isEventDispatchThread()) { 139 SwingUtilities.invokeLater(this::buildTree); 140 } else { 141 buildTree(); 142 } 143 } 144}