From fc55fff305ae2325e6c6602f96b6d135396ddd85 Mon Sep 17 00:00:00 2001 From: Marin Date: Mon, 2 Dec 2013 10:28:45 +0200 Subject: [PATCH] Fixes the issue with OTR padlock icon not being transparent when displaying .gifs for animation. Instead of .gif, a series of .png images will be displayed in a predetermined sequence. --- resources/images/images.properties | 3 + resources/images/plugin/otr/padlock1.png | Bin 0 -> 455 bytes resources/images/plugin/otr/padlock2.png | Bin 0 -> 454 bytes resources/images/plugin/otr/padlock3.png | Bin 0 -> 452 bytes .../plugin/desktoputil/AnimatedImage.java | 443 ++++++++++++++++++ .../plugin/otr/OtrMetaContactButton.java | 72 ++- 6 files changed, 507 insertions(+), 11 deletions(-) create mode 100644 resources/images/plugin/otr/padlock1.png create mode 100644 resources/images/plugin/otr/padlock2.png create mode 100644 resources/images/plugin/otr/padlock3.png create mode 100644 src/net/java/sip/communicator/plugin/desktoputil/AnimatedImage.java 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 0000000000000000000000000000000000000000..1d201dc2ca379e45376cca196e1b591a8d322906 GIT binary patch literal 455 zcmV;&0XY7NP)epQu(YtU<0@TEG@%W$8r3X7lo+nT2xgn%ChWGRdB9Ep64P-UcQ4M zXdr)OO1x)`3&vOv&hbp(1j>Q@ohh-`5prSM_8Xk<06ySeDMi%gzy)+Rij7oCY8LaC zvx|tYsWBVcPlc}#`_>=Ep{sLCd^nf9qz>;iHj{XkeSrNV*={jMyb8ncvv&hOb%6Yy zWtn(QQ;`C2@49Y6kX2I2zP;>Af`*d@c; z$IP3xtns)4tN|N9IDuP$3%CI+EyGwRN%B>crKsy#R8=hwheL;|f_){5q7Z5N@*PD{ z1Nkdc;tgY5Fvfatj(Y+pP!8n#ro>@K$c1g&Z?L}uYytO5DfVp+96)=caHUdGvzR`d zT||6Mjd5u`7F&gQYyDyDx;nSSySd~gb=cR~OyXJg0oIRXyTugoDvsmN-VOZR0rGvG z=i)WXLk>WP)arXvpZBAA(Cb#zS>4?QtbJTwMtjF%8zJa{2VsLfQGH>a2>9Y1k{ ze8)#!5o%r0egI1GV+{GL>iX1zbEWVw={?VXilS`qUWrP1N}%=R0pyBFAKQ-OJRK_; w6djjF^>AB;2hD}m&bmz}hZn%pk^YbP1xo;R<=TU4mH+?%07*qoM6N<$g0Af`*d@c; z$IP3xtns)4tN|N9Fo9cu3%CI+EyGyHar~L(xhTt06h$e{=W~OifPEpdEE7rc^0nXZ zE687&5^osef-%;DbKDa+fpQ?gbv%?s{URxN8j77%w5bc<@4!P@AbVZ%#2&I{wQI z@*N*_MWA&>{Q)S&k1^!04%f`M#vUfUrRjH;<@MbwQ7BIdw4PK;u9)<(?KsX;UrDE^ uUmDfJbr~Ks7gjy%Hk}+?08dByKjH`M-gnu)NR~PP0000 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();