/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.plugin.desktoputil; import java.awt.*; import javax.swing.*; import net.java.sip.communicator.util.skin.*; import org.jitsi.service.resources.*; /** * Represents the sound level indicator for a particular peer. * * @author Dilshan Amadoru * @author Yana Stamcheva * @author Adam Netocny * @author Lyubomir Marinov */ public class SoundLevelIndicator extends TransparentPanel implements Skinnable { /** * Serial version UID. */ private static final long serialVersionUID = 0L; private static final String SOUND_LEVEL_ACTIVE_LEFT = "service.gui.soundlevel.SOUND_LEVEL_ACTIVE_LEFT"; private static final String SOUND_LEVEL_ACTIVE_LEFT_GRADIENT = "service.gui.soundlevel.SOUND_LEVEL_ACTIVE_LEFT_GRADIENT"; private static final String SOUND_LEVEL_ACTIVE_MIDDLE = "service.gui.soundlevel.SOUND_LEVEL_ACTIVE_MIDDLE"; private static final String SOUND_LEVEL_ACTIVE_RIGHT = "service.gui.soundlevel.SOUND_LEVEL_ACTIVE_RIGHT"; private static final String SOUND_LEVEL_ACTIVE_RIGHT_GRADIENT = "service.gui.soundlevel.SOUND_LEVEL_ACTIVE_RIGHT_GRADIENT"; private static final String SOUND_LEVEL_INACTIVE_LEFT = "service.gui.soundlevel.SOUND_LEVEL_INACTIVE_LEFT"; private static final String SOUND_LEVEL_INACTIVE_MIDDLE = "service.gui.soundlevel.SOUND_LEVEL_INACTIVE_MIDDLE"; private static final String SOUND_LEVEL_INACTIVE_RIGHT = "service.gui.soundlevel.SOUND_LEVEL_INACTIVE_RIGHT"; /** * A runnable that will be used to update the sound level. */ private final LevelUpdate levelUpdate = new LevelUpdate(); /** * The <tt>Runnable</tt> which schedules the execution of * {@link #levelUpdate}. Introduced to better the garbage collection profile * of the utilization of <tt>LowPriorityEventQueue</tt>. * * @see LowPriorityEventQueue#createRepetitiveInvokeLater(Runnable) */ private Runnable levelUpdateScheduler; /** * The maximum possible sound level. */ private final int maxSoundLevel; /** * The minimum possible sound level. */ private final int minSoundLevel; /** * The number of (distinct) sound bars displayed by this instance. */ private int soundBarCount; /** * The sound level which is currently depicted by this * <tt>SoundLevelIndicator</tt>. */ private int soundLevel; /** * Image when a sound level block is active */ private ImageIcon soundLevelActiveImageLeft; /** * Image when a sound level block is active */ private ImageIcon soundLevelActiveImageLeftGradient; /** * Image when a sound level block is active */ private ImageIcon soundLevelActiveImageMiddle; /** * Image when a sound level block is active */ private ImageIcon soundLevelActiveImageRight; /** * Image when a sound level block is active */ private ImageIcon soundLevelActiveImageRightGradient; /** * Image when a sound level block is not active */ private ImageIcon soundLevelInactiveImageLeft; /** * Image when a sound level block is not active */ private ImageIcon soundLevelInactiveImageMiddle; /** * Image when a sound level block is not active */ private ImageIcon soundLevelInactiveImageRight; /** * Initializes a new <tt>SoundLevelIndicator</tt> instance. * * @param minSoundLevel the minimum possible sound level * @param maxSoundLevel the maximum possible sound level */ public SoundLevelIndicator(int minSoundLevel, int maxSoundLevel) { this.minSoundLevel = minSoundLevel; this.maxSoundLevel = maxSoundLevel; this.soundLevel = minSoundLevel; loadSkin(); setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); } /** * Returns the number of sound level bars that we could currently show in * this panel. * * @param width the current width of the call window * @return the number of sound level bars that we could currently show in * this panel */ private int getSoundBarCount(int width) { int soundBarWidth = soundLevelActiveImageLeft.getIconWidth(); return width / soundBarWidth; } /** * Reloads icons. */ public void loadSkin() { ResourceManagementService resources = DesktopUtilActivator.getResources(); soundLevelActiveImageLeft = resources.getImage(SOUND_LEVEL_ACTIVE_LEFT); soundLevelActiveImageLeftGradient = resources.getImage(SOUND_LEVEL_ACTIVE_LEFT_GRADIENT); soundLevelActiveImageMiddle = resources.getImage(SOUND_LEVEL_ACTIVE_MIDDLE); soundLevelActiveImageRight = resources.getImage(SOUND_LEVEL_ACTIVE_RIGHT); soundLevelActiveImageRightGradient = resources.getImage(SOUND_LEVEL_ACTIVE_RIGHT_GRADIENT); soundLevelInactiveImageLeft = resources.getImage(SOUND_LEVEL_INACTIVE_LEFT); soundLevelInactiveImageMiddle = resources.getImage(SOUND_LEVEL_INACTIVE_MIDDLE); soundLevelInactiveImageRight = resources.getImage(SOUND_LEVEL_INACTIVE_RIGHT); if (!isPreferredSizeSet()) { int preferredHeight = 0; int preferredWidth = 0; if (soundLevelActiveImageLeft != null) { int height = soundLevelActiveImageLeft.getIconHeight(); int width = soundLevelActiveImageLeft.getIconWidth(); if (preferredHeight < height) preferredHeight = height; if (preferredWidth < width) preferredWidth = width; } if (soundLevelInactiveImageLeft != null) { int height = soundLevelInactiveImageLeft.getIconHeight(); int width = soundLevelInactiveImageLeft.getIconWidth(); if (preferredHeight < height) preferredHeight = height; if (preferredWidth < width) preferredWidth = width; } if ((preferredHeight > 0) && (preferredWidth > 0)) setPreferredSize( new Dimension( 10 * preferredWidth, preferredHeight)); } updateSoundLevel(soundLevel); } public void resetSoundLevel() { soundLevel = minSoundLevel; updateSoundLevel(minSoundLevel); } @Override public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); int newSoundBarCount = getSoundBarCount(getWidth()); if (newSoundBarCount > 0) { while (newSoundBarCount < soundBarCount) { for (int i = getComponentCount() - 1; i >= 0; i--) { Component c = getComponent(i); if (c instanceof JLabel) { remove(c); soundBarCount--; break; } } } while (soundBarCount < newSoundBarCount) { JLabel soundBar; if (soundBarCount == 0) soundBar = new JLabel(soundLevelInactiveImageLeft); else if (soundBarCount == newSoundBarCount - 1) soundBar = new JLabel(soundLevelInactiveImageRight); else soundBar = new JLabel(soundLevelInactiveImageMiddle); soundBar.setVerticalAlignment(JLabel.CENTER); add(soundBar); soundBarCount++; } } updateSoundLevel(soundLevel); revalidate(); repaint(); } /** * Update the sound level indicator component to fit the given values. * * @param soundLevel the sound level to show */ public void updateSoundLevel(int soundLevel) { levelUpdate.setSoundLevel(soundLevel); Runnable levelUpdateScheduler; synchronized (this) { if (this.levelUpdateScheduler == null) { this.levelUpdateScheduler = LowPriorityEventQueue.createRepetitiveInvokeLater( levelUpdate); } levelUpdateScheduler = this.levelUpdateScheduler; } levelUpdateScheduler.run(); } /** * Update the sound level indicator component to fit the given values. * * @param soundLevel the sound level to show */ private void updateSoundLevelInternal(int soundLevel) { int range = 1; // Check if the given range values are correct. if ((minSoundLevel > -1) && (maxSoundLevel > -1) && (minSoundLevel < maxSoundLevel)) { range = maxSoundLevel - minSoundLevel; if (soundLevel < 40 /* A WHISPER */) soundLevel = minSoundLevel; else if (soundLevel > 85 /* BEGINNING OF HEARING DAMAGE */) soundLevel = maxSoundLevel; else { /* * Depict the range between "A WHISPER" and "BEGINNING OF * HEARING DAMAGE". */ soundLevel = (int) (((soundLevel - 40.0) / 45.0) * range); if (soundLevel < minSoundLevel) soundLevel = minSoundLevel; else if (soundLevel > maxSoundLevel) soundLevel = maxSoundLevel; } } /* * Audacity uses 0.9 for this.soundLevel and, consequently, 0.1 for * soundLevel but that makes the animation too slow. */ this.soundLevel = (int) (this.soundLevel * 0.8 + soundLevel * 0.2); int activeSoundBarCount = Math.round(this.soundLevel * soundBarCount / (float) range); /* * We cannot use getComponentCount() and then call getComponent(int) * because there are multiple threads involved and the code bellow is * not executed on the UI thread i.e. ArrayIndexOutOfBounds may and do * happen. */ Component[] components = getComponents(); for (int i = 0; i < components.length; i++) { Component c = getComponent(i); if (c instanceof JLabel) { Icon activeIcon = null; Icon inactiveIcon = null; if (i == 0) { if (activeSoundBarCount == 1) activeIcon = soundLevelActiveImageLeftGradient; else { activeIcon = soundLevelActiveImageLeft; inactiveIcon = soundLevelInactiveImageLeft; } } else if (i == activeSoundBarCount - 1) { if (i == components.length - 1) activeIcon = soundLevelActiveImageRight; else activeIcon = soundLevelActiveImageRightGradient; } else if (i == components.length - 1) { inactiveIcon = soundLevelInactiveImageRight; } else { activeIcon = soundLevelActiveImageMiddle; inactiveIcon = soundLevelInactiveImageMiddle; } ((JLabel) c).setIcon( (i < activeSoundBarCount) ? activeIcon : inactiveIcon); } } repaint(); }; /** * Runnable used to update sound levels. */ private class LevelUpdate implements Runnable { /** * The current sound level to update. */ private int soundLevel; /** * Update. */ public void run() { updateSoundLevelInternal(soundLevel); } /** * Changes the sound level. * @param soundLevel changes the sound level. */ public void setSoundLevel(int soundLevel) { this.soundLevel = soundLevel; } } }