    001// License: GPL. For details, see LICENSE file.
    002package org.openstreetmap.josm.plugins.streetside.actions;
    003
    004import java.awt.image.BufferedImage;
    005
    006import javax.swing.SwingUtilities;
    007
    008import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    009import org.openstreetmap.josm.plugins.streetside.StreetsideData;
    010import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener;
    011import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
    012import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
    013import org.openstreetmap.josm.plugins.streetside.cache.CacheUtils;
    014import org.openstreetmap.josm.plugins.streetside.gui.StreetsideMainDialog;
    015
    016
    017/**
    018 * Thread containing the walk process.
    019 *
    020 * @author nokutu
    021 */
    022public class WalkThread extends Thread implements StreetsideDataListener {
    023  private final int interval;
    024  private final StreetsideData data;
    025  private boolean end;
    026  private final boolean waitForFullQuality;
    027  private final boolean followSelected;
    028  private final boolean goForward;
    029  private BufferedImage lastImage;
    030  private volatile boolean paused;
    031
    032  /**
    033   * Main constructor.
    034   *
    035   * @param interval How often the images switch.
    036   * @param waitForPicture If it must wait for the full resolution picture or just the
    037   * thumbnail.
    038   * @param followSelected Zoom to each image that is selected.
    039   * @param goForward true to go forward; false to go backwards.
    040   */
    041  public WalkThread(int interval, boolean waitForPicture,
    042                    boolean followSelected, boolean goForward) {
    043    this.interval = interval;
    044    waitForFullQuality = waitForPicture;
    045    this.followSelected = followSelected;
    046    this.goForward = goForward;
    047    data = StreetsideLayer.getInstance().getData();
    048    data.addListener(this);
    049  }
    050
    051  @Override
    052  public void run() {
    053    try {
    054      while (!end && data.getSelectedImage().next() != null) {
    055        StreetsideAbstractImage image = data.getSelectedImage();
    056        if (image != null && image.next() instanceof StreetsideImage) {
    057          // Predownload next 10 thumbnails.
    058          preDownloadImages((StreetsideImage) image.next(), 10, CacheUtils.PICTURE.THUMBNAIL);
    059          // TODO: WalkThread for cubemaps? @rrh
    060          //preDownloadCubemaps((StreetsideImage) image.next(), 10, CacheUtils.PICTURE.CUBEMAP);
    061          if (waitForFullQuality) {
    062            // Start downloading 3 next full images.
    063            StreetsideAbstractImage currentImage = image.next();
    064                preDownloadImages((StreetsideImage) currentImage, 3, CacheUtils.PICTURE.FULL_IMAGE);
    065                // TODO: WalkThread for cubemaps? @rrh
    066                /*if (StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get().booleanValue()) {
    067                  preDownloadCubemaps((StreetsideImage) currentImage, 3, CacheUtils.PICTURE.CUBEMAP);
    068            }*/
    069          }
    070        }
    071        try {
    072          // Waits for full quality picture.
    073          final BufferedImage displayImage = StreetsideMainDialog.getInstance().getStreetsideImageDisplay().getImage();
    074          if (waitForFullQuality && image instanceof StreetsideImage) {
    075            while (displayImage == lastImage || displayImage == null || displayImage.getWidth() < 2048) {
    076              Thread.sleep(100);
    077            }
    078          } else { // Waits for thumbnail.
    079            while (displayImage == lastImage || displayImage == null || displayImage.getWidth() < 320) {
    080              Thread.sleep(100);
    081            }
    082          }
    083          while (paused) {
    084            Thread.sleep(100);
    085          }
    086          wait(interval);
    087          while (paused) {
    088            Thread.sleep(100);
    089          }
    090          lastImage = StreetsideMainDialog.getInstance().getStreetsideImageDisplay().getImage();
    091          if (goForward) {
    092            data.selectNext(followSelected);
    093          } else {
    094            data.selectPrevious(followSelected);
    095          }
    096        } catch (InterruptedException e) {
    097          return;
    098        }
    099      }
    100
    101        // TODO: WalkThread for cubemaps? @rrh
    102        /*while (!end && data.getSelectedImage().next() != null) {
    103            StreetsideAbstractImage cubemap = data.getSelectedImage();
    104            if (cubemap != null && cubemap.next() instanceof StreetsideCubemap) {
    105              if (waitForFullQuality) {
    106                // Start downloading 3 next full images.
    107
    108                // TODO: cubemap handling @rrh
    109                preDownloadCubemaps((StreetsideCubemap) cubemap.next(), 6, CacheUtils.PICTURE.CUBEMAP);
    110              }
    111            }
    112            try {
    113              // Waits for full quality picture.
    114              final BufferedImage[] displayCubemap = StreetsideMainDialog.getInstance().streetsideViewerDisplay.getCubemap();
    115              if (waitForFullQuality && cubemap instanceof StreetsideCubemap) {
    116                  // TODO: handle cubemap width? @rrh
    117                  while (displayCubemap == lastCubemap || displayCubemap == null || displayCubemap.getWidth() < 2048) {
    118                  Thread.sleep(100);
    119                }
    120              } else { // Waits for thumbnail.
    121                  // TODO: handle cubemap width? @rrh
    122                  while (displayCubemap == lastCubemap || displayCubemap == null || displayCubemap.getWidth() < 320) {
    123                  Thread.sleep(100);
    124                }
    125              }
    126              while (paused) {
    127                Thread.sleep(100);
    128              }
    129              wait(interval);
    130              while (paused) {
    131                Thread.sleep(100);
    132              }
    133              lastCubemap = StreetsideMainDialog.getInstance().streetsideViewerDisplay.getCubemap();
    134              // TODO: forward / previous for cubemap? @rrh
    135              if (goForward) {
    136                data.selectNext(followSelected);
    137              } else {
    138                data.selectPrevious(followSelected);
    139              }
    140            } catch (InterruptedException e) {
    141              return;
    142            }
    143          }*/
    144    } catch (NullPointerException e) {
    145      // TODO: Avoid NPEs instead of waiting until they are thrown and then catching them
    146      return;
    147    }
    148    end();
    149  }
    150
    151  private void preDownloadCubemaps(StreetsideImage startImage, int n, CacheUtils.PICTURE type) {
    152          if (n >= 1 && startImage != null) {
    153
    154                  for (int i = 0; i < 6; i++) {
    155                                for (int j = 0; j < 4; j++) {
    156                                        for (int k = 0; k < 4; k++) {
    157
    158                                                CacheUtils.downloadPicture(startImage, type);
    159                                                if (startImage.next() instanceof StreetsideImage && n >= 2) {
    160                                                        preDownloadImages((StreetsideImage) startImage.next(), n - 1, type);
    161                                                }
    162                                        }
    163                                }
    164                  }
    165          }
    166  }
    167
    168/**
    169   * Downloads n images into the cache beginning from the supplied start-image (including the start-image itself).
    170   *
    171   * @param startImage the image to start with (this and the next n-1 images in the same sequence are downloaded)
    172   * @param n the number of images to download
    173   * @param type the quality of the image (full or thumbnail)
    174   */
    175  private static void preDownloadImages(StreetsideImage startImage, int n, CacheUtils.PICTURE type) {
    176    if (n >= 1 && startImage != null) {
    177      CacheUtils.downloadPicture(startImage, type);
    178      if (startImage.next() instanceof StreetsideImage && n >= 2) {
    179        preDownloadImages((StreetsideImage) startImage.next(), n - 1, type);
    180      }
    181    }
    182  }
    183
    184  @Override
    185  public void imagesAdded() {
    186    // Nothing
    187  }
    188
    189  @Override
    190  public void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) {
    191    if (newImage != oldImage.next()) {
    192      end();
    193      interrupt();
    194    }
    195  }
    196
    197  /**
    198   * Continues with the execution if paused.
    199   */
    200  public void play() {
    201    paused = false;
    202  }
    203
    204  /**
    205   * Pauses the execution.
    206   */
    207  public void pause() {
    208    paused = true;
    209  }
    210
    211  /**
    212   * Stops the execution.
    213   */
    214  public void stopWalk() {
    215    if (SwingUtilities.isEventDispatchThread()) {
    216      end();
    217      interrupt();
    218    } else {
    219      SwingUtilities.invokeLater(this::stopWalk);
    220    }
    221  }
    222
    223  /**
    224   * Called when the walk stops by itself of forcefully.
    225   */
    226  public void end() {
    227    if (SwingUtilities.isEventDispatchThread()) {
    228      end = true;
    229      data.removeListener(this);
    230      StreetsideMainDialog.getInstance().setMode(StreetsideMainDialog.MODE.NORMAL);
    231    } else {
    232      SwingUtilities.invokeLater(this::end);
    233    }
    234    // TODO: WalkThread for Cubemaps? @rrh
    235    /*if (Platform.isEventDispatchThread()) {
    236        end = true;
    237        data.removeListener(this);
    238        StreetsideViewerDialog.getInstance().setMode(StreetsideViewerDialog.MODE.NORMAL);
    239      } else {
    240        Platform.invokeLater(this::end);
    241      }*/
    242  }
    243}