001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.plugins.streetside.actions;
003
004import java.awt.image.BufferedImage;
005import java.text.MessageFormat;
006
007import javax.swing.SwingUtilities;
008
009import org.apache.log4j.Logger;
010import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
011import org.openstreetmap.josm.plugins.streetside.StreetsideData;
012import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener;
013import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
014import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
015import org.openstreetmap.josm.plugins.streetside.cache.CacheUtils;
016import org.openstreetmap.josm.plugins.streetside.gui.StreetsideMainDialog;
017import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
018
019
020/**
021 * Thread containing the walk process.
022 *
023 * @author nokutu
024 */
025public class WalkThread extends Thread implements StreetsideDataListener {
026  private final int interval;
027  private final StreetsideData data;
028  private boolean end;
029  private final boolean waitForFullQuality;
030  private final boolean followSelected;
031  private final boolean goForward;
032  private BufferedImage lastImage;
033  private volatile boolean paused;
034
035  final static Logger logger = Logger.getLogger(WalkThread.class);
036
037  /**
038   * Main constructor.
039   *
040   * @param interval How often the images switch.
041   * @param waitForPicture If it must wait for the full resolution picture or just the
042   * thumbnail.
043   * @param followSelected Zoom to each image that is selected.
044   * @param goForward true to go forward; false to go backwards.
045   */
046  public WalkThread(int interval, boolean waitForPicture,
047                    boolean followSelected, boolean goForward) {
048    this.interval = interval;
049    waitForFullQuality = waitForPicture;
050    this.followSelected = followSelected;
051    this.goForward = goForward;
052    data = StreetsideLayer.getInstance().getData();
053    data.addListener(this);
054  }
055
056  @Override
057  public void run() {
058    try {
059      while (!end && data.getSelectedImage().next() != null) {
060        StreetsideAbstractImage image = data.getSelectedImage();
061        if (image != null && image.next() instanceof StreetsideImage) {
062          // Predownload next 10 thumbnails.
063          preDownloadImages((StreetsideImage) image.next(), 10, CacheUtils.PICTURE.THUMBNAIL);
064          if(StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get()) {
065            preDownloadCubemaps((StreetsideImage) image.next(), 10);
066          }
067          if (waitForFullQuality) {
068            // Start downloading 3 next full images.
069            StreetsideAbstractImage currentImage = image.next();
070                  preDownloadImages((StreetsideImage) currentImage, 3, CacheUtils.PICTURE.FULL_IMAGE);
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      if(StreetsideProperties.DEBUGING_ENABLED.get()) {
104        logger.debug(MessageFormat.format("Exception thrown in WalkThread: {0}", e.getMessage()));
105        e.printStackTrace();
106      }
107      return;
108    }
109    end();
110  }
111
112  private void preDownloadCubemaps(StreetsideImage startImage, int n) {
113          if (n >= 1 && startImage != null) {
114
115                  for (int i = 0; i < 6; i++) {
116                                for (int j = 0; j < 4; j++) {
117                                        for (int k = 0; k < 4; k++) {
118
119                                                CacheUtils.downloadPicture(startImage, CacheUtils.PICTURE.CUBEMAP);
120                                                if (startImage.next() instanceof StreetsideImage && n >= 2) {
121                                                        preDownloadCubemaps((StreetsideImage) startImage.next(), n - 1);
122                                                }
123                                        }
124                                }
125                  }
126          }
127  }
128
129/**
130   * Downloads n images into the cache beginning from the supplied start-image (including the start-image itself).
131   *
132   * @param startImage the image to start with (this and the next n-1 images in the same sequence are downloaded)
133   * @param n the number of images to download
134   * @param type the quality of the image (full or thumbnail)
135   */
136  private static void preDownloadImages(StreetsideImage startImage, int n, CacheUtils.PICTURE type) {
137    if (n >= 1 && startImage != null) {
138      CacheUtils.downloadPicture(startImage, type);
139      if (startImage.next() instanceof StreetsideImage && n >= 2) {
140        preDownloadImages((StreetsideImage) startImage.next(), n - 1, type);
141      }
142    }
143  }
144
145  @Override
146  public void imagesAdded() {
147    // Nothing
148  }
149
150  @Override
151  public void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) {
152    if (newImage != oldImage.next()) {
153      end();
154      interrupt();
155    }
156  }
157
158  /**
159   * Continues with the execution if paused.
160   */
161  public void play() {
162    paused = false;
163  }
164
165  /**
166   * Pauses the execution.
167   */
168  public void pause() {
169    paused = true;
170  }
171
172  /**
173   * Stops the execution.
174   */
175  public void stopWalk() {
176    if (SwingUtilities.isEventDispatchThread()) {
177      end();
178      interrupt();
179    } else {
180      SwingUtilities.invokeLater(this::stopWalk);
181    }
182  }
183
184  /**
185   * Called when the walk stops by itself of forcefully.
186   */
187  public void end() {
188    if (SwingUtilities.isEventDispatchThread()) {
189      end = true;
190      data.removeListener(this);
191      StreetsideMainDialog.getInstance().setMode(StreetsideMainDialog.MODE.NORMAL);
192    } else {
193      SwingUtilities.invokeLater(this::end);
194    }
195  }
196}