/* * Copyright (c) 2007, Romain Guy * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.clothocore.tool.pluginmanager.gui; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Composite; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagLayout; import java.awt.Image; import java.awt.Insets; import java.awt.Paint; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.util.HashMap; import org.clothocore.api.core.Collator; import java.util.ArrayList; import java.util.Arrays; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import javax.swing.JPanel; import javax.swing.Timer; import org.clothocore.api.data.ObjType; import org.clothocore.util.misc.BareBonesBrowserLaunch; import org.clothocore.util.basic.ImageSource; /** * This is the panel with the images and the button label above them * @author jcanderson */ public class ChoosePreferredViewers extends JPanel implements FancyPanel { private Hashtable<Integer, String> labels = new Hashtable<Integer, String>(); private Hashtable<String, String> descriptions = new Hashtable<String, String>(); private RightClickPopup rcPopup; private static final double ANIM_SCROLL_DELAY = 450; private List<Image> avatars = null; private boolean loadingDone = false; private Thread picturesFinder = null; private Timer scrollerTimer = null; private Timer faderTimer = null; private Timer passwordTimer; private float veilAlphaLevel = 0.0f; private float alphaLevel = 0.0f; private float textAlphaLevel = 0.0f; private int avatarIndex; private double avatarPosition = 0.0; private double avatarSpacing = 0.4; private int avatarAmount = 5; private double sigma; private double rho; private double exp_multiplier; private double exp_member; private boolean damaged = true; private DrawableAvatar[] drawableAvatars; private String textAvatar; private String descriptionAvatar; private FocusGrabber focusGrabber; private AvatarScroller avatarScroller; private CursorChanger cursorChanger; private MouseWheelScroller wheelScroller; private KeyScroller keyScroller; private ApplicationFrame _app; public ChoosePreferredViewers(ApplicationFrame app) { _app = app; descriptions.put("PART", "Compositional primitives such as BioBricks"); descriptions.put("VECTOR", "Backbone portion of a DNA composition"); descriptions.put("PLASMID", "A complete DNA composition"); descriptions.put("OLIGO", "Theoretical oligonucleotides"); descriptions.put("NOTE", "Information related to a topic including factoids"); descriptions.put("FACTOID", "Nugget of information related to a reference"); descriptions.put("CONTAINER", "Vessels containing samples such as barcoded wells and microcentrifuge tubes"); descriptions.put("SAMPLE", "Liquid containing a biological entity"); descriptions.put("SAMPLE_DATA", "Sangar sequencing read with a .abi file"); descriptions.put("PERSON", "A human being"); descriptions.put("LAB", "Group of people that do research"); descriptions.put("INSTITUTION", "University, college, institute, or company"); descriptions.put("PLATE", "A vessel with arrayed containers"); descriptions.put("PLATE_TYPE", "Physical configuration and appearance of a plate"); descriptions.put("FORMAT", "Compositional standards such as RFC10"); descriptions.put("STRAIN", "Cells or other clonal biological species"); descriptions.put("FEATURE", "Biological primitives such as a GFP coding sequence"); descriptions.put("FAMILY", "Biological ontology relationships between features"); descriptions.put("GRAMMAR", "Grammatical rules for combining parts"); descriptions.put("COLLECTION", "User-defined list of related objects"); descriptions.put("NUCSEQ", "DNA or RNA sequence"); descriptions.put("ANNOTATION", "A labeled region of a sequence, often with a feature"); descriptions.put("WIKITEXT", "A formatted text region with images and attachments"); descriptions.put("ATTACHMENT", "An image, genbank file, or any other file type"); descriptions.put("FLEX_FIELD", "Custom data for plugins"); GridBagLayout layout = new GridBagLayout(); setLayout(layout); findAvatars(); setSigma(0.5); addComponentListener(new DamageManager()); initInputListeners(); addInputListeners(); } public void setAmount(int amount) { if (amount > avatars.size()) { throw new IllegalArgumentException("Too many avatars"); } this.avatarAmount = amount; repaint(); } // XXX package access for debugging purpose only public void setPosition(double position) { this.avatarPosition = position; this.damaged = true; repaint(); } public void setSigma(double sigma) { this.sigma = sigma; this.rho = 1.0; computeEquationParts(); this.rho = computeModifierUnprotected(0.0); computeEquationParts(); this.damaged = true; repaint(); } public void setSpacing(double avatarSpacing) { if (avatarSpacing < 0.0 || avatarSpacing > 1.0) { throw new IllegalArgumentException("Spacing must be < 1.0 and > 0.0"); } this.avatarSpacing = avatarSpacing; this.damaged = true; repaint(); } @Override public Dimension getPreferredSize() { return new Dimension(128 * 3, 128 * 2); } @Override public Dimension getMaximumSize() { return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); } @Override public boolean isOpaque() { return false; } @Override public boolean isFocusable() { return true; } @Override protected void paintChildren(Graphics g) { Graphics2D g2 = (Graphics2D) g; Composite oldComposite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, veilAlphaLevel)); super.paintChildren(g); g2.setComposite(oldComposite); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (!loadingDone && faderTimer == null) { return; } Insets insets = getInsets(); int x = insets.left; int y = insets.top; int width = getWidth() - insets.left - insets.right; int height = getHeight() - insets.top - insets.bottom; Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Composite oldComposite = g2.getComposite(); if (damaged) { drawableAvatars = sortAvatarsByDepth(x, y, width, height); damaged = false; } drawAvatars(g2, drawableAvatars); if (drawableAvatars.length > 0) { drawAvatarName(g2); } g2.setComposite(oldComposite); } private void drawAvatars(Graphics2D g2, DrawableAvatar[] drawableAvatars) { for (DrawableAvatar avatar: drawableAvatars) { AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) avatar.getAlpha()); g2.setComposite(composite); g2.drawImage(avatars.get(avatar.getIndex()), (int) avatar.getX(), (int) avatar.getY(), avatar.getWidth(), avatar.getHeight(), null); } } private DrawableAvatar[] sortAvatarsByDepth(int x, int y, int width, int height) { List<DrawableAvatar> drawables = new LinkedList<DrawableAvatar>(); for (int i = 0; i < avatars.size(); i++) { promoteAvatarToDrawable(drawables, x, y, width, height, i - avatarIndex); } DrawableAvatar[] drawable_avatars = new DrawableAvatar[drawables.size()]; drawable_avatars = drawables.toArray(drawable_avatars); Arrays.sort(drawable_avatars); return drawable_avatars; } //This is the label over the images, or at least where its size is set private void drawAvatarName(Graphics2D g2) { Composite composite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, textAlphaLevel)); //This is the height and width of the little bullet double bulletWidth = 150.0; double bulletHeight = 30.0; double x = (getWidth() - bulletWidth) / 2.0; double y = (getHeight() - 154) / 2.0 - bulletHeight * 1.4; drawAvatarBullet(g2, x, y, bulletWidth, bulletHeight); drawAvatarText(g2, y, bulletHeight); drawDescriptionText(g2, y, bulletHeight); g2.setComposite(composite); } //Writes the actual text of the avatar. textAvatar is the String it writes private void drawAvatarText(Graphics2D g2, double y, double bulletHeight) { FontRenderContext context = g2.getFontRenderContext(); Font font = new Font("Dialog", Font.PLAIN, 18); TextLayout layout = new TextLayout(textAvatar, font, context); Rectangle2D bounds = layout.getBounds(); float text_x = (float) ((getWidth() - bounds.getWidth()) / 2.0); float text_y = (float) (y + (bulletHeight - layout.getAscent() - layout.getDescent()) / 2.0) + layout.getAscent() - layout.getLeading(); g2.setColor(Color.BLACK); layout.draw(g2, text_x, text_y + 1); g2.setColor(Color.WHITE); layout.draw(g2, text_x, text_y); } //Writes the Description, added by JCA private void drawDescriptionText(Graphics2D g2, double y, double bulletHeight) { FontRenderContext context = g2.getFontRenderContext(); Font font = new Font("Dialog", Font.PLAIN, 15); TextLayout layout = new TextLayout(descriptionAvatar, font, context); Rectangle2D bounds = layout.getBounds(); float text_x = (float) ((getWidth() - bounds.getWidth()) / 2.0); float text_y = (float) (y + (bulletHeight - layout.getAscent() - layout.getDescent()) / 2.0) + layout.getAscent() - layout.getLeading() + 40; g2.setColor(Color.BLACK); layout.draw(g2, text_x, text_y + 1); g2.setColor(new Color(180,180,180)); layout.draw(g2, text_x, text_y); } //JCA this is the little label over the images with the name like "part" private void drawAvatarBullet(Graphics2D g2, double x, double y, double bulletWidth, double bulletHeight) { RoundRectangle2D bullet = new RoundRectangle2D.Double(0.0, 0.0, bulletWidth, bulletHeight, bulletHeight, bulletHeight); Ellipse2D curve = new Ellipse2D.Double(-20.0, bulletHeight / 2.0, bulletWidth + 120.0, bulletHeight); g2.translate(x, y); g2.translate(-1, -2); g2.setColor(new Color(0, 0, 0, 170)); g2.fill(new RoundRectangle2D.Double(0.0, 0.0, bulletWidth + 2, bulletHeight + 4, bulletHeight + 4, bulletHeight + 4)); g2.translate(1, 2); Color startColor = new Color(30, 30, 30); Color endColor = new Color(79, 83, 77); Paint paint = g2.getPaint(); g2.setPaint(new GradientPaint(0.0f, 0.0f, startColor, 0.0f, (float) bulletHeight, endColor)); g2.fill(bullet); startColor = new Color(50, 50, 50); endColor = new Color(70, 70, 70); g2.setPaint(new GradientPaint(0.0f, 0.0f, startColor, 0.0f, (float) bulletHeight, endColor)); Area area = new Area(bullet); area.intersect(new Area(curve)); g2.fill(area); g2.setPaint(paint); g2.translate(-x, -y); } private void promoteAvatarToDrawable(List<DrawableAvatar> drawables, int x, int y, int width, int height, int offset) { double spacing = offset * avatarSpacing; double avatarPos = this.avatarPosition + spacing; if (avatarIndex + offset < 0 || avatarIndex + offset >= avatars.size()) { return; } Image avatar = avatars.get(avatarIndex + offset); int avatarWidth = avatar.getWidth(null); int avatarHeight = avatar.getHeight(null); double result = computeModifier(avatarPos); int newWidth = (int) (avatarWidth * result); if (newWidth == 0) { return; } int newHeight = (int) (avatarHeight * result); if (newHeight == 0) { return; } //This is what determines the position of the avatars on the JFrame, is all relative to size of frame double avatar_x = x + (width - newWidth) / 2.0; double avatar_y = y + (height - newHeight / 2.0) / 1.7; double semiWidth = width / 2.0; avatar_x += avatarPos * semiWidth; if (avatar_x >= width || avatar_x < -newWidth) { return; } drawables.add(new DrawableAvatar(avatarIndex + offset, avatar_x, avatar_y, newWidth, newHeight, avatarPos, result)); } private void startFader() { faderTimer = new Timer(35, new FaderAction()); faderTimer.start(); } private void computeEquationParts() { exp_multiplier = Math.sqrt(2.0 * Math.PI) / sigma / rho; exp_member = 4.0 * sigma * sigma; } // XXX package access for debug purpose only public double computeModifier(double x) { double result = computeModifierUnprotected(x); if (result > 1.0) { result = 1.0; } else if (result < -1.0) { result = -1.0; } return result; } private double computeModifierUnprotected(double x) { return exp_multiplier * Math.exp((-x * x) / exp_member); } private void addInputListeners() { addMouseListener(focusGrabber); addMouseListener(avatarScroller); addMouseMotionListener(cursorChanger); addMouseWheelListener(wheelScroller); addKeyListener(keyScroller); } private void initInputListeners() { // input listeners are all stateless // hence they can be instantiated once focusGrabber = new FocusGrabber(); avatarScroller = new AvatarScroller(); cursorChanger = new CursorChanger(); wheelScroller = new MouseWheelScroller(); keyScroller = new KeyScroller(); } private void removeInputListeners() { removeMouseListener(focusGrabber); removeMouseListener(avatarScroller); removeMouseMotionListener(cursorChanger); removeMouseWheelListener(wheelScroller); removeKeyListener(keyScroller); } private void findAvatars() { avatars = new ArrayList<Image>(); picturesFinder = new Thread(new PicturesFinderThread()); picturesFinder.start(); } private void setAvatarIndex(int index) { avatarIndex = index; //This is where the label gets set textAvatar = labels.get(index); descriptionAvatar = descriptions.get(textAvatar); } private void scrollBy(int increment) { if (loadingDone) { setAvatarIndex(avatarIndex + increment); if (avatarIndex < 0) { setAvatarIndex(0); } else if (avatarIndex >= avatars.size()) { setAvatarIndex(avatars.size() - 1); } damaged = true; repaint(); } } private void scrollAndAnimateBy(int increment) { if (loadingDone && (scrollerTimer == null || !scrollerTimer.isRunning())) { int index = avatarIndex + increment; if (index < 0) { index = 0; } else if (index >= avatars.size()) { index = avatars.size() - 1; } DrawableAvatar drawable = null; for (DrawableAvatar avatar: drawableAvatars) { if (avatar.index == index) { drawable = avatar; break; } } if (drawable != null) { scrollAndAnimate(drawable); } } } private void scrollAndAnimate(DrawableAvatar avatar) { if (loadingDone) { scrollerTimer = new Timer(10, new AutoScroller(avatar)); scrollerTimer.start(); } } private BufferedImage createReflectedPicture(BufferedImage avatar) { int avatarWidth = avatar.getWidth(); int avatarHeight = avatar.getHeight(); BufferedImage alphaMask = createGradientMask(avatarWidth, avatarHeight); return createReflectedPicture(avatar, alphaMask); } private BufferedImage createReflectedPicture(BufferedImage avatar, BufferedImage alphaMask) { int avatarWidth = avatar.getWidth(); int avatarHeight = avatar.getHeight(); BufferedImage buffer = createReflection(avatar, avatarWidth, avatarHeight); applyAlphaMask(buffer, alphaMask, avatarWidth, avatarHeight); return buffer; } private void applyAlphaMask(BufferedImage buffer, BufferedImage alphaMask, int avatarWidth, int avatarHeight) { Graphics2D g2 = buffer.createGraphics(); g2.setComposite(AlphaComposite.DstOut); g2.drawImage(alphaMask, null, 0, avatarHeight); g2.dispose(); } private BufferedImage createReflection(BufferedImage avatar, int avatarWidth, int avatarHeight) { BufferedImage buffer = new BufferedImage(avatarWidth, avatarHeight << 1, BufferedImage.TYPE_INT_ARGB); Graphics2D g = buffer.createGraphics(); g.drawImage(avatar, null, null); g.translate(0, avatarHeight << 1); AffineTransform reflectTransform = AffineTransform.getScaleInstance(1.0, -1.0); g.drawImage(avatar, reflectTransform, null); g.translate(0, -(avatarHeight << 1)); g.dispose(); return buffer; } private BufferedImage createGradientMask(int avatarWidth, int avatarHeight) { BufferedImage gradient = new BufferedImage(avatarWidth, avatarHeight, BufferedImage.TYPE_INT_ARGB); Graphics2D g = gradient.createGraphics(); GradientPaint painter = new GradientPaint(0.0f, 0.0f, new Color(1.0f, 1.0f, 1.0f, 0.5f), 0.0f, avatarHeight / 2.0f, new Color(1.0f, 1.0f, 1.0f, 1.0f)); g.setPaint(painter); g.fill(new Rectangle2D.Double(0, 0, avatarWidth, avatarHeight)); g.dispose(); return gradient; } private DrawableAvatar getHitAvatar(int x, int y) { for (DrawableAvatar avatar: drawableAvatars) { Rectangle hit = new Rectangle((int) avatar.getX(), (int) avatar.getY(), avatar.getWidth(), avatar.getHeight() / 2); if (hit.contains(x, y)) { return avatar; } } return null; } private class PicturesFinderThread implements Runnable { @Override public void run() { HashMap<ObjType, BufferedImage> imgHash = ImageSource.getObjectImageSet(100); for (int i = 0; i < imgHash.size(); i++) { BufferedImage image = imgHash.get(ObjType.values()[i]); avatars.add(createReflectedPicture(image)); String filename = ObjType.values()[i].toString() + ".png"; labels.put(i, ObjType.values()[i].toString() ); if (i == (imgHash.size() / 2) + avatarAmount / 2) { setAvatarIndex(i - avatarAmount / 2); startFader(); } } loadingDone = true; } } private class FaderAction implements ActionListener { private long start = 0; private FaderAction() { alphaLevel = 0.0f; textAlphaLevel = 0.0f; } @Override public void actionPerformed(ActionEvent e) { if (start == 0) { start = System.currentTimeMillis(); } alphaLevel = (System.currentTimeMillis() - start) / 500.0f; textAlphaLevel = alphaLevel; if (alphaLevel > 1.0f) { alphaLevel = 1.0f; textAlphaLevel = 1.0f; faderTimer.stop(); } repaint(); } } private class DrawableAvatar implements Comparable { private int index; private double x; private double y; private int width; private int height; private double zOrder; private double position; private DrawableAvatar(int index, double x, double y, int width, int height, double position, double zOrder) { this.index = index; this.x = x; this.y = y; this.width = width; this.height = height; this.position = position; this.zOrder = zOrder; } public int compareTo(Object o) { double zOrder2 = ((DrawableAvatar) o).zOrder; if (zOrder < zOrder2) { return -1; } else if (zOrder > zOrder2) { return 1; } return 0; } public double getPosition() { return position; } public double getAlpha() { return zOrder * alphaLevel; } public int getHeight() { return height; } public int getIndex() { return index; } public int getWidth() { return width; } public double getX() { return x; } public double getY() { return y; } } private class MouseWheelScroller implements MouseWheelListener { public void mouseWheelMoved(MouseWheelEvent e) { int increment = e.getWheelRotation(); scrollAndAnimateBy(increment); } } private class KeyScroller extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode(); switch (keyCode) { case KeyEvent.VK_LEFT: case KeyEvent.VK_UP: scrollAndAnimateBy(-1); break; case KeyEvent.VK_RIGHT: case KeyEvent.VK_DOWN: scrollAndAnimateBy(1); break; case KeyEvent.VK_END: scrollBy(avatars.size() - avatarIndex - 1); break; case KeyEvent.VK_HOME: scrollBy(-avatarIndex - 1); break; case KeyEvent.VK_PAGE_UP: scrollAndAnimateBy(-avatarAmount / 2); break; case KeyEvent.VK_PAGE_DOWN: scrollAndAnimateBy(avatarAmount / 2); break; } } } private class FocusGrabber extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { requestFocus(); } } private class AvatarScroller extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { if(rcPopup!=null) { rcPopup.remove(); } if ((faderTimer != null && faderTimer.isRunning()) || (scrollerTimer != null && scrollerTimer.isRunning()) || drawableAvatars == null) { return; } //Scroll to that avatar DrawableAvatar avatar = getHitAvatar(e.getX(), e.getY()); if (avatar != null && avatar.getIndex() != avatarIndex) { scrollAndAnimate(avatar); return; } if(e.getY()<50) { _app.close(); } //If it's a double-click, open the help page describing the part type if(e.getClickCount()==2) { //BareBonesBrowserLaunch.openURL(Collator.helpURLBase + "help/ObjectDescriptions/" + textAvatar + ".html"); BareBonesBrowserLaunch.openURL(Collator.helpURLBase + "/" + textAvatar.toLowerCase()); return; } //If it's a right click, make a popup window and allow user to put in default viewer String objtypestring = textAvatar; if(objtypestring.equals("sequenceRead")) { objtypestring = "SEQUENCE_READ"; } if(objtypestring.equals("plateType")) { objtypestring = "PLATE_TYPE"; } if(e.getModifiers()==4) { rcPopup = new RightClickPopup(e.getLocationOnScreen().x, e.getLocationOnScreen().y, objtypestring); return; } } } private class DamageManager extends ComponentAdapter { @Override public void componentResized(ComponentEvent e) { damaged = true; } } private class AutoScroller implements ActionListener { private double position; private int index; private long start; private AutoScroller(DrawableAvatar avatar) { this.index = avatar.getIndex(); this.position = avatar.getPosition(); this.start = System.currentTimeMillis(); } public void actionPerformed(ActionEvent e) { long elapsed = System.currentTimeMillis() - start; if (elapsed < ANIM_SCROLL_DELAY / 2.0) { textAlphaLevel = (float) (1.0 - 2.0 * (elapsed / ANIM_SCROLL_DELAY)); } else { textAlphaLevel = (float) (((elapsed / ANIM_SCROLL_DELAY) - 0.5) * 2.0); if (textAlphaLevel > 1.0f) { textAlphaLevel = 1.0f; } } if (textAlphaLevel < 0.1f) { textAlphaLevel = 0.1f; textAvatar = " "; } double newPosition = (elapsed / ANIM_SCROLL_DELAY) * -position; if (elapsed >= ANIM_SCROLL_DELAY) { ((Timer) e.getSource()).stop(); setAvatarIndex(index); setPosition(0.0); return; } setPosition(newPosition); } } private class CursorChanger extends MouseMotionAdapter { @Override public void mouseMoved(MouseEvent e) { if ((scrollerTimer != null && scrollerTimer.isRunning()) || drawableAvatars == null) { return; } DrawableAvatar avatar = getHitAvatar(e.getX(), e.getY()); if (avatar != null) { getParent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else { getParent().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } } } }