<commit_before>package org.micromanager.imagedisplay;

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

import java.awt.Dimension;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JPanel;

import org.micromanager.utils.ReportingUtils;

/**
 * This class is responsible for containing and managing groups of 
 * AxisScrollers, and how they affect the display of a collection of images.
 */
public class ScrollerPanel extends JPanel {
   
   /**
    * This class signifies that the currently-displayed image needs to be 
    * updated.
    */
   public static class SetImageEvent {
      // Maps axis labels to their positions. 
      private HashMap<String, Integer> axisToPosition_;
      public SetImageEvent(HashMap<String, Integer> axisToPosition) {
         axisToPosition_ = axisToPosition;
      }
      /**
       * Retrieve the desired position along the specified axis, or 0 if we 
       * don't have a marker for that axis.
       */
      public Integer getPositionForAxis(String axis) {
         if (axisToPosition_.containsKey(axis)) {
            return axisToPosition_.get(axis);
         }
         return 0;
      }
   }

   /**
    * This class signifies that our layout has changed and our owner needs to 
    * revalidate.
    */
   public static class LayoutChangedEvent {}

   // We'll be communicating with our owner and with our AxisScrollers via
   // this bus.
   private EventBus bus_;
   // All AxisScrollers we manage.
   private ArrayList<AxisScroller> scrollers_;
   // Timer for handling animation.
   private Timer animationUpdateTimer_ = null;
   // Timer for restoring scrollbars after forcing their positions.
   private Timer snapBackTimer_ = null;
   // Rate at which we update images when animating. Defaults to 10.
   private double framesPerSec_;

   /**
    * @param axes List of Strings labeling the axes that the caller wants to 
    *        create AxisScrollers for. 
    * @param maximums List of Integers indicating the number of images along
    *        that axis. 
    */
   public ScrollerPanel(EventBus bus, String[] axes, Integer[] maximums, 
         double framesPerSec) {
      // Minimize whitespace around our components.
      super(new net.miginfocom.swing.MigLayout("insets 0, fillx"));
      
      bus_ = bus;
      bus_.register(this);
      framesPerSec_ = framesPerSec;
      scrollers_ = new ArrayList<AxisScroller>();

      // Create all desired AxisScrollers. Use the first character of the 
      // axis as the label. Default all scrollers to invisible unless they have
      // at least 2, um, "ticks"; they'll be shown once there's more than one
      // option along that axis.
      // TODO: for now assuming all axes can animate.
      for (int i = 0; i < maximums.length; ++i) {
         int max = maximums[i];
         if (max < 1) {
            // Scroll bars do not allow zero "ticks".
            max = 1;
         }
         AxisScroller scroller = new AxisScroller(axes[i], 
               max, bus, true);
         if (max <= 1) {
            scroller.setVisible(false);
         }
         else {
            add(scroller, "wrap 0px, align center, growx");
         }
         scrollers_.add(scroller);
      }
   }

   /**
    * One of our AxisScrollers changed position; update the image.
    */
   @Subscribe
   public void onScrollPositionChanged(AxisScroller.ScrollPositionEvent event) {
      postSetImageEvent();
   }

   private void postSetImageEvent() {
      HashMap<String, Integer> axisToPosition = new HashMap<String, Integer>();
      for (AxisScroller scroller : scrollers_) {
         axisToPosition.put(scroller.getAxis(), scroller.getPosition());
      }
      bus_.post(new SetImageEvent(axisToPosition));
   }

   /**
    * One of our AxisScrollers toggled animation status; replace our
    * animation timer.
    */
   @Subscribe
   public void onAnimationToggle(AxisScroller.AnimationToggleEvent event) {
      resetAnimationTimer();
   }

   /**
    * The window we're in is closing; cancel animations and timers.
    */
   public void prepareForClose() {
      for (AxisScroller scroller : scrollers_) {
         scroller.setIsAnimated(false);
      }
      if (animationUpdateTimer_ != null) {
         animationUpdateTimer_.cancel();
      }
      if (snapBackTimer_ != null) {
         snapBackTimer_.cancel();
      }
   }

   /**
    * Generate a new AnimationTimer that updates each active (i.e. animated)
    * scroller according to our update rate (FPS). 
    */
   private void resetAnimationTimer() {
      if (animationUpdateTimer_ != null) {
         // Stop the previous timer.
         animationUpdateTimer_.cancel();
      }
      animationUpdateTimer_ = new Timer();
      // Enforce a maximum displayed framerate of 30FPS; for higher rates, we
      // instead skip over images in animation.
      int stepSize = 1;
      long interval = (long) (1000.0 / framesPerSec_);
      if (interval < 33) {
         interval = 33; 
         stepSize = (int) Math.round(framesPerSec_ * 33.0 / 1000.0);
      }
      // This is going to be how much we adjust each scroller's position each
      // tick of the animation.
      final int[] offsets = new int[scrollers_.size()];
      for (int i = 0; i < offsets.length; ++i) {
         // Let me just take a moment to note here that Java
         // can't cast a boolean to an int.
         offsets[i] = (scrollers_.get(i).getIsAnimated() ? 1 : 0) * stepSize;
      }
      TimerTask task = new TimerTask() {
         @Override
         public void run() {
            for (int i = 0; i < scrollers_.size(); ++i) {
               if (offsets[i] != 0) {
                  // Note that the scroller handles wrapping around to the 
                  // beginning, and also whether or not to move at all due to
                  // being locked. 
                  scrollers_.get(i).advancePosition(offsets[i], false);
               }
            }
            postSetImageEvent();
         }
      };
      animationUpdateTimer_.schedule(task, 0, interval);
   }

   /**
    * A new image has been made available; we need to adjust our scrollbars
    * to suit. We only show the new image (i.e. update scrollbar positions)
    * if none of our scrollers are superlocked.
    */
   @Subscribe
   public void onNewImageEvent(NewImageEvent event) {
      boolean didShowNewScrollers = false;
      boolean canShowNewImage = true;<commit_msg>Fix bug causing cursor in comments text to constantly move to front of box.<commit_after>package org.micromanager.imagedisplay;

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

import java.awt.Dimension;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JPanel;

import org.micromanager.utils.ReportingUtils;

/**
 * This class is responsible for containing and managing groups of 
 * AxisScrollers, and how they affect the display of a collection of images.
 */
public class ScrollerPanel extends JPanel {
   
   /**
    * This class signifies that the currently-displayed image needs to be 
    * updated.
    */
   public static class SetImageEvent {
      // Maps axis labels to their positions. 
      private HashMap<String, Integer> axisToPosition_;
      public SetImageEvent(HashMap<String, Integer> axisToPosition) {
         axisToPosition_ = axisToPosition;
      }
      /**
       * Retrieve the desired position along the specified axis, or 0 if we 
       * don't have a marker for that axis.
       */
      public Integer getPositionForAxis(String axis) {
         if (axisToPosition_.containsKey(axis)) {
            return axisToPosition_.get(axis);
         }
         return 0;
      }
   }

   /**
    * This class signifies that our layout has changed and our owner needs to 
    * revalidate.
    */
   public static class LayoutChangedEvent {}

   // We'll be communicating with our owner and with our AxisScrollers via
   // this bus.
   private EventBus bus_;
   // All AxisScrollers we manage.
   private ArrayList<AxisScroller> scrollers_;
   // A mapping of axis identifiers to their positions as of the last time
   // checkForImagePositionChanged() was called.
   private HashMap<String, Integer> lastImagePosition_ = null;
   // Timer for handling animation.
   private Timer animationUpdateTimer_ = null;
   // Timer for restoring scrollbars after forcing their positions.
   private Timer snapBackTimer_ = null;
   // Rate at which we update images when animating. Defaults to 10.
   private double framesPerSec_;

   /**
    * @param axes List of Strings labeling the axes that the caller wants to 
    *        create AxisScrollers for. 
    * @param maximums List of Integers indicating the number of images along
    *        that axis. 
    */
   public ScrollerPanel(EventBus bus, String[] axes, Integer[] maximums, 
         double framesPerSec) {
      // Minimize whitespace around our components.
      super(new net.miginfocom.swing.MigLayout("insets 0, fillx"));
      
      bus_ = bus;
      bus_.register(this);
      framesPerSec_ = framesPerSec;
      scrollers_ = new ArrayList<AxisScroller>();

      // Create all desired AxisScrollers. Use the first character of the 
      // axis as the label. Default all scrollers to invisible unless they have
      // at least 2, um, "ticks"; they'll be shown once there's more than one
      // option along that axis.
      // TODO: for now assuming all axes can animate.
      for (int i = 0; i < maximums.length; ++i) {
         int max = maximums[i];
         if (max < 1) {
            // Scroll bars do not allow zero "ticks".
            max = 1;
         }
         AxisScroller scroller = new AxisScroller(axes[i], 
               max, bus, true);
         if (max <= 1) {
            scroller.setVisible(false);
         }
         else {
            add(scroller, "wrap 0px, align center, growx");
         }
         scrollers_.add(scroller);
      }
   }

   /**
    * One of our AxisScrollers changed position; update the image.
    */
   @Subscribe
   public void onScrollPositionChanged(AxisScroller.ScrollPositionEvent event) {
      checkForImagePositionChanged();
   }

   /**
    * Check to see if the image we are currently "pointing to" with the 
    * scrollers is different from the image that we were last pointing to
    * when this function was called. If so, then we need to post a
    * SetImageEvent to the event bus so that the image display gets updated.
    */
   private void checkForImagePositionChanged() {
      boolean shouldPostEvent = false;
      if (lastImagePosition_ == null) {
         lastImagePosition_ = new HashMap<String, Integer>();
      }
      for (AxisScroller scroller : scrollers_) {
         String axis = scroller.getAxis();
         Integer position = scroller.getPosition();
         if (!lastImagePosition_.containsKey(axis) ||
               lastImagePosition_.get(axis) != position) {
            // Position along this axis has changed; we need to refresh.
            shouldPostEvent = true;
         }
         lastImagePosition_.put(axis, position);
      }
      if (shouldPostEvent) {
         bus_.post(new SetImageEvent(lastImagePosition_));
      }
   }

   /**
    * One of our AxisScrollers toggled animation status; replace our
    * animation timer.
    */
   @Subscribe
   public void onAnimationToggle(AxisScroller.AnimationToggleEvent event) {
      resetAnimationTimer();
   }

   /**
    * The window we're in is closing; cancel animations and timers.
    */
   public void prepareForClose() {
      for (AxisScroller scroller : scrollers_) {
         scroller.setIsAnimated(false);
      }
      if (animationUpdateTimer_ != null) {
         animationUpdateTimer_.cancel();
      }
      if (snapBackTimer_ != null) {
         snapBackTimer_.cancel();
      }
   }

   /**
    * Generate a new AnimationTimer that updates each active (i.e. animated)
    * scroller according to our update rate (FPS). 
    */
   private void resetAnimationTimer() {
      if (animationUpdateTimer_ != null) {
         // Stop the previous timer.
         animationUpdateTimer_.cancel();
      }
      // Enforce a maximum displayed framerate of 30FPS; for higher rates, we
      // instead skip over images in animation.
      int stepSize = 1;
      long interval = (long) (1000.0 / framesPerSec_);
      if (interval < 33) {
         interval = 33; 
         stepSize = (int) Math.round(framesPerSec_ * 33.0 / 1000.0);
      }
      boolean isAnimated = false;
      // This is going to be how much we adjust each scroller's position each
      // tick of the animation.
      final int[] offsets = new int[scrollers_.size()];
      for (int i = 0; i < offsets.length; ++i) {
         if (scrollers_.get(i).getIsAnimated()) {
            isAnimated = true;
            offsets[i] = stepSize;
         }
         else {
            offsets[i] = 0;
         }
      }
      if (isAnimated) {
         animationUpdateTimer_ = new Timer();
         TimerTask task = new TimerTask() {
            @Override
            public void run() {
               for (int i = 0; i < scrollers_.size(); ++i) {
                  if (offsets[i] != 0) {
                     // Note that the scroller handles wrapping around to the 
                     // beginning, and also whether or not to move at all due to
                     // being locked. 
                     scrollers_.get(i).advancePosition(offsets[i], false);
                  }
               }
               checkForImagePositionChanged();
            }
         };
         animationUpdateTimer_.schedule(task, 0, interval);
      }
   }

   /**
    * A new image has been made available; we need to adjust our scrollbars
    * to suit. We only show the new image (i.e. update scrollbar positions)
    * if none of our scrollers are superlocked.
    */
   @Subscribe
   public void onNewImageEvent(NewImageEvent event) {
      boolean didShowNewScrollers = false;
      boolean canShowNewImage = true;
{'fraction_non_alphanumeric': 0.08742320457594803, 'fraction_numerical': 0.005155003177741685, 'mean_word_length': 2.2348104157149384, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1216694', 'n_tokens_mistral': 3979, 'n_tokens_neox': 3780, 'n_words': 1318}