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}