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}