StreetsideMainDialog.java
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.streetside.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import org.openstreetmap.josm.data.cache.CacheEntry;
import org.openstreetmap.josm.data.cache.CacheEntryAttributes;
import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
import org.openstreetmap.josm.gui.SideButton;
import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener;
import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
import org.openstreetmap.josm.plugins.streetside.StreetsideImportedImage;
import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
import org.openstreetmap.josm.plugins.streetside.StreetsidePlugin;
import org.openstreetmap.josm.plugins.streetside.actions.WalkListener;
import org.openstreetmap.josm.plugins.streetside.actions.WalkThread;
import org.openstreetmap.josm.plugins.streetside.cache.StreetsideCache;
import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.ImageInfoHelpPopup;
import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.StreetsideViewerHelpPopup;
import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
import org.openstreetmap.josm.tools.I18n;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.Logging;
/**
* Toggle dialog that shows an image and some buttons.
*
* @author nokutu
* @author renerr18
*/
public final class StreetsideMainDialog extends ToggleDialog implements
ICachedLoaderListener, StreetsideDataListener {
private static final long serialVersionUID = 2645654786827812861L;
public static final String BASE_TITLE = I18n.marktr("Microsoft Streetside image");
private static final String MESSAGE_SEPARATOR = " — ";
private static StreetsideMainDialog instance;
private volatile StreetsideAbstractImage image;
public final SideButton nextButton = new SideButton(new NextPictureAction());
public final SideButton previousButton = new SideButton(new PreviousPictureAction());
/**
* Button used to jump to the image following the red line
*/
public final SideButton redButton = new SideButton(new RedAction());
/**
* Button used to jump to the image following the blue line
*/
public final SideButton blueButton = new SideButton(new BlueAction());
private final SideButton playButton = new SideButton(new PlayAction());
private final SideButton pauseButton = new SideButton(new PauseAction());
private final SideButton stopButton = new SideButton(new StopAction());
private ImageInfoHelpPopup imageInfoHelp;
private StreetsideViewerHelpPopup streetsideViewerHelp;
/**
* Buttons mode.
*
* @author nokutu
*/
public enum MODE {
/**
* Standard mode to view pictures.
*/
NORMAL,
/**
* Mode when in walk.
*/
WALK
}
/**
* Object containing the shown image and that handles zoom and drag
*/
public StreetsideImageDisplay streetsideImageDisplay;
private StreetsideCache imageCache;
public StreetsideCache thumbnailCache;
private StreetsideMainDialog() {
super(I18n.tr(StreetsideMainDialog.BASE_TITLE), "streetside-main", I18n.tr("Open Streetside window"), null, 200,
true, StreetsidePreferenceSetting.class);
addShortcuts();
streetsideImageDisplay = new StreetsideImageDisplay();
blueButton.setForeground(Color.BLUE);
redButton.setForeground(Color.RED);
// TODO: Modes for cubemaps? @rrh
setMode(MODE.NORMAL);
}
/**
* Adds the shortcuts to the buttons.
*/
private void addShortcuts() {
nextButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke("PAGE_DOWN"), "next");
nextButton.getActionMap().put("next", new NextPictureAction());
previousButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke("PAGE_UP"), "previous");
previousButton.getActionMap().put("previous",
new PreviousPictureAction());
blueButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke("control PAGE_UP"), "blue");
blueButton.getActionMap().put("blue", new BlueAction());
redButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke("control PAGE_DOWN"), "red");
redButton.getActionMap().put("red", new RedAction());
}
/**
* Returns the unique instance of the class.
*
* @return The unique instance of the class.
*/
public static synchronized StreetsideMainDialog getInstance() {
if (StreetsideMainDialog.instance == null) {
StreetsideMainDialog.instance = new StreetsideMainDialog();
}
return StreetsideMainDialog.instance;
}
/**
* @return true, iff the singleton instance is present
*/
public static boolean hasInstance() {
return StreetsideMainDialog.instance != null;
}
public synchronized void setImageInfoHelp(ImageInfoHelpPopup popup) {
imageInfoHelp = popup;
}
public synchronized void setStreetsideViewerHelp(StreetsideViewerHelpPopup popup) {
streetsideViewerHelp = popup;
}
/**
* @return the streetsideViewerHelp
*/
public StreetsideViewerHelpPopup getStreetsideViewerHelp() {
return streetsideViewerHelp;
}
/**
* Sets a new mode for the dialog.
*
* @param mode The mode to be set. Must not be {@code null}.
*/
public void setMode(MODE mode) {
switch (mode) {
case WALK:
createLayout(
streetsideImageDisplay,
Arrays.asList(playButton, pauseButton, stopButton)
);
case NORMAL:
default:
createLayout(
streetsideImageDisplay,
Arrays.asList(blueButton, previousButton, nextButton, redButton)
);
}
disableAllButtons();
if (MODE.NORMAL.equals(mode)) {
updateImage();
} }
/**
* Destroys the unique instance of the class.
*/
public static synchronized void destroyInstance() {
StreetsideMainDialog.instance = null;
}
/**
* Downloads the full quality picture of the selected StreetsideImage and sets
* in the StreetsideImageDisplay object.
*/
public synchronized void updateImage() {
updateImage(true);
}
/**
* Downloads the picture of the selected StreetsideImage and sets in the
* StreetsideImageDisplay object.
*
* @param fullQuality If the full quality picture must be downloaded or just the
* thumbnail.
*/
public synchronized void updateImage(boolean fullQuality) {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(this::updateImage);
} else {
if (!StreetsideLayer.hasInstance()) {
return;
}
if (image == null) {
streetsideImageDisplay.setImage(null, null);
setTitle(I18n.tr(StreetsideMainDialog.BASE_TITLE));
disableAllButtons();
return;
}
// TODO: help for cubemaps? @rrh
if (imageInfoHelp != null && StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN.get() > 0 && imageInfoHelp.showPopup()) {
// Count down the number of times the popup will be displayed
StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN.put(StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN.get() - 1);
}
// Enables/disables next/previous buttons
nextButton.setEnabled(false);
previousButton.setEnabled(false);
if (image.getSequence() != null) {
StreetsideAbstractImage tempImage = image;
while (tempImage.next() != null) {
tempImage = tempImage.next();
if (tempImage.isVisible()) {
nextButton.setEnabled(true);
break;
}
}
}
if (image.getSequence() != null) {
StreetsideAbstractImage tempImage = image;
while (tempImage.previous() != null) {
tempImage = tempImage.previous();
if (tempImage.isVisible()) {
previousButton.setEnabled(true);
break;
}
}
}
if (image instanceof StreetsideImage) {
final StreetsideImage streetsideImage = (StreetsideImage) image;
// Downloads the thumbnail.
streetsideImageDisplay.setImage(null, null);
if (thumbnailCache != null) {
thumbnailCache.cancelOutstandingTasks();
}
thumbnailCache = new StreetsideCache(streetsideImage.getId(),
StreetsideCache.Type.THUMBNAIL);
try {
thumbnailCache.submit(this, false);
} catch (final IOException e) {
Logging.error(e);
}
// Downloads the full resolution image.
if (fullQuality || new StreetsideCache(streetsideImage.getId(),
StreetsideCache.Type.FULL_IMAGE).get() != null) {
if (imageCache != null) {
imageCache.cancelOutstandingTasks();
}
imageCache = new StreetsideCache(streetsideImage.getId(),
StreetsideCache.Type.FULL_IMAGE);
try {
imageCache.submit(this, false);
} catch (final IOException e) {
Logging.error(e);
}
}
// TODO: handle/convert/remove "imported images"
} /*else if (image instanceof StreetsideImportedImage) {
final StreetsideImportedImage streetsideImage = (StreetsideImportedImage) image;
try {
streetsideImageDisplay.setImage(streetsideImage.getImage(), null);
} catch (final IOException e) {
Logging.error(e);
}
}*/
updateTitle();
}
}
/**
* Disables all the buttons in the dialog
*/
public /*private*/ void disableAllButtons() {
nextButton.setEnabled(false);
previousButton.setEnabled(false);
blueButton.setEnabled(false);
redButton.setEnabled(false);
}
/**
* Sets a new StreetsideImage to be shown.
*
* @param image The image to be shown.
*/
public synchronized void setImage(StreetsideAbstractImage image) {
this.image = image;
}
/**
* Updates the title of the dialog.
*/
// TODO: update title for 360 degree viewer? @rrh
public synchronized void updateTitle() {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(this::updateTitle);
} else if (image != null) {
final StringBuilder title = new StringBuilder(I18n.tr(StreetsideMainDialog.BASE_TITLE));
if (image instanceof StreetsideImage) {
final StreetsideImage streetsideImage = (StreetsideImage) image;
if (streetsideImage.getCd() != 0) {
title.append(StreetsideMainDialog.MESSAGE_SEPARATOR).append(streetsideImage.getDate());
}
setTitle(title.toString());
} else if (image instanceof StreetsideImportedImage) {
final StreetsideImportedImage mapillaryImportedImage = (StreetsideImportedImage) image;
title.append(StreetsideMainDialog.MESSAGE_SEPARATOR).append(mapillaryImportedImage.getFile().getName());
title.append(StreetsideMainDialog.MESSAGE_SEPARATOR).append(mapillaryImportedImage.getDate());
setTitle(title.toString());
}
}
}
/**
* Returns the {@link StreetsideAbstractImage} object which is being shown.
*
* @return The {@link StreetsideAbstractImage} object which is being shown.
*/
public synchronized StreetsideAbstractImage getImage() {
return image;
}
/**
* Action class form the next image button.
*
* @author nokutu
*/
private static class NextPictureAction extends AbstractAction {
private static final long serialVersionUID = 6333692154558730392L;
/**
* Constructs a normal NextPictureAction
*/
NextPictureAction() {
super(I18n.tr("Next picture"));
putValue(Action.SHORT_DESCRIPTION, I18n.tr("Shows the next picture in the sequence"));
new ImageProvider("help", "next").getResource().attachImageIcon(this, true);
}
@Override
public void actionPerformed(ActionEvent e) {
StreetsideLayer.getInstance().getData().selectNext();
}
}
/**
* Action class for the previous image button.
*
* @author nokutu
*/
private static class PreviousPictureAction extends AbstractAction {
private static final long serialVersionUID = 4390593660514657107L;
/**
* Constructs a normal PreviousPictureAction
*/
PreviousPictureAction() {
super(I18n.tr("Previous picture"));
putValue(Action.SHORT_DESCRIPTION, I18n.tr("Shows the previous picture in the sequence"));
new ImageProvider("help", "previous").getResource().attachImageIcon(this, true);
}
@Override
public void actionPerformed(ActionEvent e) {
StreetsideLayer.getInstance().getData().selectPrevious();
}
}
/**
* Action class to jump to the image following the red line.
*
* @author nokutu
*/
private static class RedAction extends AbstractAction {
private static final long serialVersionUID = -1244456062285831231L;
/**
* Constructs a normal RedAction
*/
RedAction() {
putValue(Action.NAME, I18n.tr("Jump to red"));
putValue(Action.SHORT_DESCRIPTION,
I18n.tr("Jumps to the picture at the other side of the red line"));
new ImageProvider("dialogs", "red").getResource().attachImageIcon(this, true);
}
// TODO: RedAction for cubemaps? @rrh
@Override
public void actionPerformed(ActionEvent e) {
if (StreetsideMainDialog.getInstance().getImage() != null) {
StreetsideLayer.getInstance().getData()
.setSelectedImage(StreetsideLayer.getInstance().getNNearestImage(1), true);
}
}
}
/**
* Action class to jump to the image following the blue line.
*
* @author nokutu
*/
private static class BlueAction extends AbstractAction {
private static final long serialVersionUID = 5951233534212838780L;
/**
* Constructs a normal BlueAction
*/
BlueAction() {
putValue(Action.NAME, I18n.tr("Jump to blue"));
putValue(Action.SHORT_DESCRIPTION,
I18n.tr("Jumps to the picture at the other side of the blue line"));
new ImageProvider("dialogs", "blue").getResource().attachImageIcon(this, true);
}
// TODO: BlueAction for cubemaps?
@Override
public void actionPerformed(ActionEvent e) {
if (StreetsideMainDialog.getInstance().getImage() != null) {
StreetsideLayer.getInstance().getData()
.setSelectedImage(StreetsideLayer.getInstance().getNNearestImage(2), true);
}
}
}
private static class StopAction extends AbstractAction implements WalkListener {
private static final long serialVersionUID = 8789972456611625341L;
private WalkThread thread;
/**
* Constructs a normal StopAction
*/
StopAction() {
putValue(Action.NAME, I18n.tr("Stop"));
putValue(Action.SHORT_DESCRIPTION, I18n.tr("Stops the walk."));
new ImageProvider("dialogs/streetsideStop.png").getResource().attachImageIcon(this, true);
StreetsidePlugin.getStreetsideWalkAction().addListener(this);
}
@Override
public void actionPerformed(ActionEvent e) {
if (thread != null) {
thread.stopWalk();
}
}
@Override
public void walkStarted(WalkThread thread) {
this.thread = thread;
}
}
private static class PlayAction extends AbstractAction implements WalkListener {
private static final long serialVersionUID = -1572747020946842769L;
private transient WalkThread thread;
/**
* Constructs a normal PlayAction
*/
PlayAction() {
putValue(Action.NAME, I18n.tr("Play"));
putValue(Action.SHORT_DESCRIPTION, I18n.tr("Continues with the paused walk."));
new ImageProvider("dialogs/streetsidePlay.png").getResource().attachImageIcon(this, true);
StreetsidePlugin.getStreetsideWalkAction().addListener(this);
}
@Override
public void actionPerformed(ActionEvent e) {
if (thread != null) {
thread.play();
}
}
@Override
public void walkStarted(WalkThread thread) {
if (thread != null) {
this.thread = thread;
}
}
}
private static class PauseAction extends AbstractAction implements WalkListener {
/**
*
*/
private static final long serialVersionUID = -8758326399460817222L;
private WalkThread thread;
/**
* Constructs a normal PauseAction
*/
PauseAction() {
putValue(Action.NAME, I18n.tr("Pause"));
putValue(Action.SHORT_DESCRIPTION, I18n.tr("Pauses the walk."));
new ImageProvider("dialogs/streetsidePause.png").getResource().attachImageIcon(this, true);
StreetsidePlugin.getStreetsideWalkAction().addListener(this);
}
@Override
public void actionPerformed(ActionEvent e) {
thread.pause();
}
@Override
public void walkStarted(WalkThread thread) {
this.thread = thread;
}
}
/**
* When the pictures are returned from the cache, they are set in the
* {@link StreetsideImageDisplay} object.
*/
@Override
public void loadingFinished(final CacheEntry data, final CacheEntryAttributes attributes, final LoadResult result) {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(() -> loadingFinished(data, attributes, result));
} else if (data != null && result == LoadResult.SUCCESS) {
try {
final BufferedImage img = ImageIO.read(new ByteArrayInputStream(data.getContent()));
if (img == null) {
return;
}
if (
streetsideImageDisplay.getImage() == null
|| img.getHeight() > streetsideImageDisplay.getImage().getHeight()
) {
//final StreetsideAbstractImage mai = getImage();
streetsideImageDisplay.setImage(
img,
//mai instanceof StreetsideImage ? ((StreetsideImage) getImage()).getDetections() : null
null);
}
} catch (final IOException e) {
Logging.error(e);
}
}
}
/**
* Creates the layout of the dialog.
*
* @param data The content of the dialog
* @param buttons The buttons where you can click
*/
public void createLayout(Component data, List<SideButton> buttons) {
removeAll();
createLayout(data, true, buttons);
add(titleBar, BorderLayout.NORTH);
}
@Override
public void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) {
setImage(newImage);
updateImage();
}
@Override
public void imagesAdded() {
// This method is enforced by StreetsideDataListener, but only selectedImageChanged() is needed
}
/**
* @return the streetsideImageDisplay
*/
public StreetsideImageDisplay getStreetsideImageDisplay() {
return streetsideImageDisplay;
}
/**
* @param streetsideImageDisplay the streetsideImageDisplay to set
*/
public void setStreetsideImageDisplay(StreetsideImageDisplay streetsideImageDisplay) {
this.streetsideImageDisplay = streetsideImageDisplay;
}
/**
* @return the streetsideImageDisplay
*/
/*public StreetsideViewerDisplay getStreetsideViewerDisplay() {
return streetsideViewerDisplay;
}*/
/**
* @param streetsideImageDisplay the streetsideImageDisplay to set
*/
/*public void setStreetsideViewerDisplay(StreetsideViewerDisplay streetsideViewerDisplay) {
streetsideViewerDisplay = streetsideViewerDisplay;
}*/
/*private StreetsideViewerDisplay initStreetsideViewerDisplay() {
StreetsideViewerDisplay res = new StreetsideViewerDisplay();
//this.add(streetsideViewerDisplay);
Platform.runLater(new Runnable() {
@Override
public void run() {
Scene scene;
try {
scene = StreetsideViewerDisplay.createScene();
res.setScene(scene);
} catch (NonInvertibleTransformException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
return res;
}*/
}