    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}