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