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
016import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
017
018
019/**
020 * Thread containing the walk process.
021 *
022 * @author nokutu
023 */
024public class WalkThread extends Thread implements StreetsideDataListener {
025  private final int interval;
026  private final StreetsideData data;
027  private boolean end;
028  private final boolean waitForFullQuality;
029  private final boolean followSelected;
030  private final boolean goForward;
031  private BufferedImage lastImage;
032  private volatile boolean paused;
033
034  /**
035   * Main constructor.
036   *
037   * @param interval How often the images switch.
038   * @param waitForPicture If it must wait for the full resolution picture or just the
039   * thumbnail.
040   * @param followSelected Zoom to each image that is selected.
041   * @param goForward true to go forward; false to go backwards.
042   */
043  public WalkThread(int interval, boolean waitForPicture,
044                    boolean followSelected, boolean goForward) {
045    this.interval = interval;
046    waitForFullQuality = waitForPicture;
047    this.followSelected = followSelected;
048    this.goForward = goForward;
049    data = StreetsideLayer.getInstance().getData();
050    data.addListener(this);
051  }
052
053  @Override
054  public void run() {
055    try {
056      while (!end && data.getSelectedImage().next() != null) {
057        StreetsideAbstractImage image = data.getSelectedImage();
058        if (image != null && image.next() instanceof StreetsideImage) {
059          // Predownload next 10 thumbnails.
060          preDownloadImages((StreetsideImage) image.next(), 10, CacheUtils.PICTURE.THUMBNAIL);
061          if(StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get()) {
062            preDownloadCubemaps((StreetsideImage) image.next(), 10);
063          }
064          if (waitForFullQuality) {
065            // Start downloading 3 next full images.
066            StreetsideAbstractImage currentImage = image.next();
067                  preDownloadImages((StreetsideImage) currentImage, 3, CacheUtils.PICTURE.FULL_IMAGE);
068                  /*if (StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get().booleanValue()) {
069                  preDownloadCubemaps((StreetsideImage) currentImage, 3);
070            }*/
071          }
072        }
073        try {
074          // Waits for full quality picture.
075          final BufferedImage displayImage = StreetsideMainDialog.getInstance().getStreetsideImageDisplay().getImage();
076          if (waitForFullQuality && image instanceof StreetsideImage) {
077            while (displayImage == lastImage || displayImage == null || displayImage.getWidth() < 2048) {
078              Thread.sleep(100);
079            }
080          } else { // Waits for thumbnail.
081            while (displayImage == lastImage || displayImage == null || displayImage.getWidth() < 320) {
082              Thread.sleep(100);
083            }
084          }
085          while (paused) {
086            Thread.sleep(100);
087          }
088          wait(interval);
089          while (paused) {
090            Thread.sleep(100);
091          }
092          lastImage = StreetsideMainDialog.getInstance().getStreetsideImageDisplay().getImage();
093          if (goForward) {
094            data.selectNext(followSelected);
095          } else {
096            data.selectPrevious(followSelected);
097          }
098        } catch (InterruptedException e) {
099          return;
100        }
101      }
102    } catch (NullPointerException e) {
103      // TODO: Avoid NPEs instead of waiting until they are thrown and then catching them
104      return;
105    }
106    end();
107  }
108
109  private void preDownloadCubemaps(StreetsideImage startImage, int n) {
110          if (n >= 1 && startImage != null) {
111
112                  for (int i = 0; i < 6; i++) {
113                                for (int j = 0; j < 4; j++) {
114                                        for (int k = 0; k < 4; k++) {
115
116                                                CacheUtils.downloadPicture(startImage, CacheUtils.PICTURE.CUBEMAP);
117                                                if (startImage.next() instanceof StreetsideImage && n >= 2) {
118                                                        preDownloadCubemaps((StreetsideImage) startImage.next(), n - 1);
119                                                }
120                                        }
121                                }
122                  }
123          }
124  }
125
126/**
127   * Downloads n images into the cache beginning from the supplied start-image (including the start-image itself).
128   *
129   * @param startImage the image to start with (this and the next n-1 images in the same sequence are downloaded)
130   * @param n the number of images to download
131   * @param type the quality of the image (full or thumbnail)
132   */
133  private static void preDownloadImages(StreetsideImage startImage, int n, CacheUtils.PICTURE type) {
134    if (n >= 1 && startImage != null) {
135      CacheUtils.downloadPicture(startImage, type);
136      if (startImage.next() instanceof StreetsideImage && n >= 2) {
137        preDownloadImages((StreetsideImage) startImage.next(), n - 1, type);
138      }
139    }
140  }
141
142  @Override
143  public void imagesAdded() {
144    // Nothing
145  }
146
147  @Override
148  public void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) {
149    if (newImage != oldImage.next()) {
150      end();
151      interrupt();
152    }
153  }
154
155  /**
156   * Continues with the execution if paused.
157   */
158  public void play() {
159    paused = false;
160  }
161
162  /**
163   * Pauses the execution.
164   */
165  public void pause() {
166    paused = true;
167  }
168
169  /**
170   * Stops the execution.
171   */
172  public void stopWalk() {
173    if (SwingUtilities.isEventDispatchThread()) {
174      end();
175      interrupt();
176    } else {
177      SwingUtilities.invokeLater(this::stopWalk);
178    }
179  }
180
181  /**
182   * Called when the walk stops by itself of forcefully.
183   */
184  public void end() {
185    if (SwingUtilities.isEventDispatchThread()) {
186      end = true;
187      data.removeListener(this);
188      StreetsideMainDialog.getInstance().setMode(StreetsideMainDialog.MODE.NORMAL);
189    } else {
190      SwingUtilities.invokeLater(this::end);
191    }
192  }
193}