diff --git a/resources/images/images.properties b/resources/images/images.properties index a08f92acc..832c1270e 100644 --- a/resources/images/images.properties +++ b/resources/images/images.properties @@ -558,6 +558,9 @@ plugin.otr.LOADING_ICON_22x22=resources/images/plugin/otr/padlockLoading.gif plugin.otr.BROKEN_ICON_22x22=resources/images/plugin/otr/padlockBrokenOpen.png plugin.otr.HELP_ICON_15x15=resources/images/plugin/otr/help15x15.png plugin.otr.MENU_ITEM_ICON_16x16=resources/images/plugin/otr/otr_menu_icon.png +plugin.otr.LOADING_ICON1_22x22=resources/images/plugin/otr/padlock1.png +plugin.otr.LOADING_ICON2_22x22=resources/images/plugin/otr/padlock2.png +plugin.otr.LOADING_ICON3_22x22=resources/images/plugin/otr/padlock3.png #Global proxy config form plugin.globalproxy.PLUGIN_ICON=resources/images/plugin/globalproxyconfig/configureIcon.png diff --git a/resources/images/plugin/otr/padlock1.png b/resources/images/plugin/otr/padlock1.png new file mode 100644 index 000000000..1d201dc2c Binary files /dev/null and b/resources/images/plugin/otr/padlock1.png differ diff --git a/resources/images/plugin/otr/padlock2.png b/resources/images/plugin/otr/padlock2.png new file mode 100644 index 000000000..c96ed55a2 Binary files /dev/null and b/resources/images/plugin/otr/padlock2.png differ diff --git a/resources/images/plugin/otr/padlock3.png b/resources/images/plugin/otr/padlock3.png new file mode 100644 index 000000000..f33219612 Binary files /dev/null and b/resources/images/plugin/otr/padlock3.png differ diff --git a/src/net/java/sip/communicator/plugin/desktoputil/AnimatedImage.java b/src/net/java/sip/communicator/plugin/desktoputil/AnimatedImage.java new file mode 100644 index 000000000..0ea0f0911 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/desktoputil/AnimatedImage.java @@ -0,0 +1,443 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.desktoputil; + +import java.awt.*; +import java.awt.event.*; +import java.awt.image.*; +import java.util.*; +import java.util.List; + +import javax.swing.*; +import javax.swing.Timer; + +/** + * AnimatedImage will display a series of Images in a predetermined sequence. + * This sequence can be configured to keep repeating or stop after a specified + * number of cycles. + * + * An AnimatedImage cannot be shared by different components. However, + * the Images added to the AnimatedImage can be shared. + * + * The animation sequence is a simple sequential display of each Image. When the + * end is reached the animation restarts at the first Image. Images are + * displayed in the order in which they were added. To create custom animation + * sequences you will need to override the getNextIconIndex() and + * isCycleCompleted() methods. + * + * @author Marin Dzhigarov + */ +public class AnimatedImage + extends BufferedImage + implements ActionListener +{ + /** + * The default delay between switching over the next image. + */ + private final static int DEFAULT_DELAY = 500; + + /** + * The default number of cycles for the animation. + * -1 means that the animation will go on forever. + */ + private final static int DEFAULT_CYCLES = -1; + + /** + * The list of actual images that are going to be displayed + */ + private List images = new ArrayList(); + + /** + * The JComponent that will display the Image + */ + private JComponent component; + + /** + * The number of cycles for the animation. + * -1 means that the animation will go on forever. + */ + private int cycles; + + private boolean showFirstImage = false; + + private int imageWidth; + + private int imageHeight; + + /** + * The index of the currently displayed Image + */ + private int currentImageIndex; + + /** + * The current number of completed cycles. The animation will go on while + * cyclesCompleted < cycles. + */ + private int cyclesCompleted; + + private boolean animationFinished = true; + + /** + * The Timer that handles switching between Images. + */ + private Timer timer; + + /** + * The Graphics2D object used for painting. + */ + private final Graphics2D g2d; + + /** + * Create an AnimatedImage that will continuously cycle with the + * default (500ms). + * + * @param component the component the image will be painted on + * @param images the Images to be painted as part of the animation + */ + public AnimatedImage(JComponent component, Image... images) + { + this(component, DEFAULT_DELAY, images); + } + + /** + * Create an AnimatedImage that will continuously cycle with the specified + * delay + * + * @param component the component the image will be painted on + * @param delay the delay between painting each image, in milli seconds + * @param images the Images to be painted as part of the animation + */ + public AnimatedImage(JComponent component, int delay, Image... images) + { + this(component, delay, DEFAULT_CYCLES, images); + } + + /** + * Create an AnimatedImage specifying the required delay between painting + * each image and the number of times to repeat the animation sequence + * + * @param component the component the image will be painted on + * @param delay the delay between painting each image, in milli seconds + * @param cycles the number of times to repeat the animation sequence + * @param images the Images to be painted as part of the animation + */ + public AnimatedImage( + JComponent component, int delay, int cycles, Image... images) + { + super( images[0].getWidth(null), + images[0].getHeight(null), + BufferedImage.TYPE_INT_ARGB); + + this.component = component; + + g2d = (Graphics2D) getGraphics(); + + setCycles( cycles ); + + for (int i = 0; i < images.length; i++) + { + if (images[i] == null) + { + throw new IllegalArgumentException("Images can not be null"); + } + else + { + addImage(images[i]); + } + } + + timer = new Timer(delay, this); + } + + /** + * Add Image to be used in the animation. + * + * @param image the image to be added + */ + public void addImage(Image image) + { + if (image != null) + { + this.images.add(image); + calculateImageDimensions(); + } + } + + /** + * Calculate the width and height of the Image based on the maximum + * width and height of any individual Image. + */ + private void calculateImageDimensions() + { + imageWidth = 0; + imageHeight = 0; + + for (Image image : images) + { + imageWidth = Math.max(imageWidth, image.getWidth(null)); + imageHeight = Math.max(imageHeight, image.getHeight(null)); + } + } + + /** + * Get the index of the currently visible Image + * + * @return the index of the Icon + */ + public int getCurrentImageIndex() + { + return currentImageIndex; + } + + /** + * Set the index of the Image to be displayed and then repaint the Image. + * + * @param index the index of the Image to be displayed + */ + public void setCurrentImageIndex(int index) + { + currentImageIndex = index; + + AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC); + g2d.setComposite(ac); + g2d.drawImage(getImage(currentImageIndex), 0, 0, component); + + component.repaint(); + } + + /** + * Get the remaining cycles to complete before the animation stops. + * + * @return the number of remaining cycles + */ + public int getRemainingCycles() + { + return cycles; + } + + /** + * Specify the number of times to repeat each animation sequence, or cycle. + * + * @param cycles the number of cycles to complete before the animation + * stops. The default is -1, which means the animation is + * continuous. + */ + public void setCycles(int cycles) + { + this.cycles = cycles; + } + + /** + * Returns the delay between painting each Image + * + * @return the delay + */ + public int getDelay() + { + return timer.getDelay(); + } + + /** + * Specify the delay between painting each Image + * + * @param delay the delay between painting each Image (in milli seconds) + */ + public void setDelay(int delay) + { + timer.setDelay(delay); + } + + /** + * Returns the Image at the specified index. + * + * @param index the index of the Image to be returned + * @return the Image at the specifed index + * @exception IndexOutOfBoundsException if the index is out of range + */ + public Image getImage(int index) + { + return images.get( index ); + } + + /** + * Returns the number of Images that are contained in the animation + * + * @return the total number of Images + */ + public int getImagesCount() + { + return images.size(); + } + + /** + * Get the showFirstImage + * + * @return the showFirstImage value + */ + public boolean isShowFirstImage() + { + return showFirstImage; + } + + /** + * Displays the first image of the animation after the last cycle has passed + * If set to false, the last image will remain set after the last + * animation cycle. + * + * @param showFirstImage true when the first image is to be displayed, + * false otherwise + */ + public void setShowFirstImage(boolean showFirstImage) + { + this.showFirstImage = showFirstImage; + } + + /** + * Pauses the animation. The animation can be restarted from the + * current Image using the restart() method. + */ + public void pause() + { + timer.stop(); + } + + /** + * Start the animation from the beginning. + */ + public void start() + { + if (!timer.isRunning()) + { + setCurrentImageIndex(0); + animationFinished = false; + cyclesCompleted = 0; + timer.start(); + } + } + + /** + * Restarts the animation from where the animation was paused. Or, if the + * animation has finished, it will be restarted from the beginning. + */ + public void restart() + { + if (!timer.isRunning()) + { + if (animationFinished) + start(); + else + timer.restart(); + } + } + + /** + * Stops the animation. The first icon will be redisplayed. + */ + public void stop() + { + timer.stop(); + setCurrentImageIndex(0); + animationFinished = true; + } + + /** + * Gets the width of this image. + * + * @return the width of the image in pixels. + */ + public int getWidth(ImageObserver obs) + { + return imageWidth; + } + + /** + * Gets the height of this image. + * + * @return the height of the image in pixels. + */ + public int getHeight(ImageObserver obs) + { + return imageHeight; + } + + /** + * Controls the image animation that is scheduled by the Timer + */ + public void actionPerformed(ActionEvent e) + { + // Display the next Image in the animation sequence + setCurrentImageIndex( + getNextImageIndex(currentImageIndex, images.size())); + + // Track the number of cycles that have been completed + if (isCycleCompleted(currentImageIndex, images.size())) + { + cyclesCompleted++; + } + + // Stop the animation when the specified number of cycles is completed + if (cycles > 0 + && cycles <= cyclesCompleted) + { + timer.stop(); + animationFinished = true; + + // Display the first Image when required + if (isShowFirstImage() + && getCurrentImageIndex() != 0) + { + new Thread(new Runnable() { + + @Override + public void run() + { + // Wait one more delay interval before displaying the + // first Image + try + { + Thread.sleep( timer.getDelay() ); + setCurrentImageIndex(0); + } + catch(InterruptedException e) {} + } + + }).start(); + } + } + } + + /** + * Gets the index of the next Image to be displayed. Normally, images will + * be displayed in the order they were added to the AnimatedImage. + * If however, a custom animation sequence is required one can extend this + * method and achieve a greater control over the animation sequence. + * + * @param currentIndex the index of the Image currently displayed + * @param imageCount the number of Images to be displayed + * @return the index of the next Image to be displayed + */ + protected int getNextImageIndex(int currentIndex, int imageCount) + { + return ++currentIndex % imageCount; + } + + /** + * Checks if the currently displayed Image is the last image of the + * animation sequence. This marks the completion of a single animation + * cycle. + * If a custom animation sequence is required one can extend this + * method to achieve a greater control over the animation sequence. + * + * @param currentIndex the index of the Image currently displayed + * @param imageCount the number of Images to be displayed + * @return the index of the next Image to be displayed + */ + protected boolean isCycleCompleted(int currentIndex, int imageCount) + { + return currentIndex == imageCount - 1; + } +} \ No newline at end of file diff --git a/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java b/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java index b5b5f125f..2984fb75f 100644 --- a/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java +++ b/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java @@ -8,6 +8,9 @@ import java.awt.*; import java.awt.event.*; +import java.io.*; + +import javax.imageio.*; import net.java.otr4j.*; import net.java.otr4j.session.*; @@ -38,6 +41,18 @@ public class OtrMetaContactButton private Contact contact; + private AnimatedImage animatedPadlockImage; + + private Image finishedPadlockImage; + + private Image verifiedLockedPadlockImage; + + private Image unverifiedLockedPadlockImage; + + private Image unlockedPadlockImage; + + private Image timedoutPadlockImage; + /** * The timer task that changes the padlock icon to "loading" and * then to "broken" if the specified timeout passed @@ -69,6 +84,7 @@ public void globalPolicyChanged() setPolicy( OtrActivator.scOtrEngine.getContactPolicy(contact)); } + public void contactVerificationStatusChanged(Contact contact) { // OtrMetaContactButton.this.contact can be null. @@ -118,6 +134,40 @@ private SIPCommButton getButton() button.setToolTipText(OtrActivator.resourceService.getI18NString( "plugin.otr.menu.OTR_TOOLTIP")); + Image i1 = null, i2 = null, i3 = null; + try + { + i1 = ImageIO.read( + OtrActivator.resourceService.getImageURL( + "plugin.otr.LOADING_ICON1_22x22")); + i2 = ImageIO.read( + OtrActivator.resourceService.getImageURL( + "plugin.otr.LOADING_ICON2_22x22")); + i3 = ImageIO.read( + OtrActivator.resourceService.getImageURL( + "plugin.otr.LOADING_ICON3_22x22")); + finishedPadlockImage = ImageIO.read( + OtrActivator.resourceService.getImageURL( + "plugin.otr.FINISHED_ICON_22x22")); + verifiedLockedPadlockImage = ImageIO.read( + OtrActivator.resourceService.getImageURL( + "plugin.otr.ENCRYPTED_ICON_22x22")); + unverifiedLockedPadlockImage = ImageIO.read( + OtrActivator.resourceService.getImageURL( + "plugin.otr.ENCRYPTED_UNVERIFIED_ICON_22x22")); + unlockedPadlockImage = ImageIO.read( + OtrActivator.resourceService.getImageURL( + "plugin.otr.PLAINTEXT_ICON_22x22")); + timedoutPadlockImage = ImageIO.read( + OtrActivator.resourceService.getImageURL( + "plugin.otr.BROKEN_ICON_22x22")); + } catch (IOException e) + { + logger.debug("Failed to load padlock image"); + } + + animatedPadlockImage = new AnimatedImage(button, i1, i2, i3); + button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) @@ -216,34 +266,36 @@ private void setPolicy(OtrPolicy contactPolicy) */ private void setStatus(ScSessionStatus status) { - String urlKey; + animatedPadlockImage.pause(); + Image image; String tipKey; switch (status) { case ENCRYPTED: - urlKey + image = OtrActivator.scOtrKeyManager.isVerified(contact) - ? "plugin.otr.ENCRYPTED_ICON_22x22" - : "plugin.otr.ENCRYPTED_UNVERIFIED_ICON_22x22"; + ? verifiedLockedPadlockImage + : unverifiedLockedPadlockImage; tipKey = OtrActivator.scOtrKeyManager.isVerified(contact) ? "plugin.otr.menu.VERIFIED" : "plugin.otr.menu.UNVERIFIED"; break; case FINISHED: - urlKey = "plugin.otr.FINISHED_ICON_22x22"; + image = finishedPadlockImage; tipKey = "plugin.otr.menu.FINISHED"; break; case PLAINTEXT: - urlKey = "plugin.otr.PLAINTEXT_ICON_22x22"; + image = unlockedPadlockImage; tipKey = "plugin.otr.menu.START_OTR"; break; case LOADING: - urlKey = "plugin.otr.LOADING_ICON_22x22"; + image = animatedPadlockImage; + animatedPadlockImage.start(); tipKey = "plugin.otr.menu.LOADING_OTR"; break; case TIMED_OUT: - urlKey = "plugin.otr.BROKEN_ICON_22x22"; + image = timedoutPadlockImage; tipKey = "plugin.otr.menu.TIMED_OUT"; break; default: @@ -251,9 +303,7 @@ private void setStatus(ScSessionStatus status) } SIPCommButton button = getButton(); - button.setIconImage( - Toolkit.getDefaultToolkit().getImage( - OtrActivator.resourceService.getImageURL(urlKey))); + button.setIconImage(image); button.setToolTipText(OtrActivator.resourceService .getI18NString(tipKey)); button.repaint();