/* * 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.impl.gui.main.call.conference; import java.awt.*; import javax.swing.*; import javax.swing.text.*; import net.java.sip.communicator.impl.gui.main.call.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.service.resources.*; import net.java.sip.communicator.util.skin.*; import org.jitsi.service.neomedia.*; /** * The basic panel used to render any conference participant. Meant to be * extended for <tt>CallPeer</tt>s and <tt>ConferenceMember</tt>s. * * @author Yana Stamcheva * @author Adam Netocny * @author Lyubomir Marinov */ public abstract class BasicConferenceParticipantPanel<T> extends TransparentPanel implements Skinnable { /** * The avatar icon height. */ private static final int AVATAR_HEIGHT = 50; /** * The avatar icon width. */ private static final int AVATAR_WIDTH = 50; /** * Background color. */ private static final Color bgColor = new Color(110, 110, 110); /** * Serial version UID. */ private static final long serialVersionUID = 0L; /** * The <tt>CallRenderer</tt> which (directly or indirectly) initialized this * instance and which uses it to depict the associated * conference participant. */ private final SwingCallRenderer callRenderer; /** * The status of the peer */ private final JLabel callStatusLabel = new JLabel(); /** * Main panel constraints. */ private final GridBagConstraints constraints = new GridBagConstraints(); /** * The component responsible for displaying an error message. */ private JTextComponent errorMessageComponent; /** * True if the avatar icon was changed and is no more default. */ private boolean iconChanged = false; /** * The label showing the image of the participant. */ private final JLabel imageLabel = new JLabel(); /** * Indicates if we're in a video interface. */ private final boolean isVideo; /** * The name bar. */ private final JPanel nameBar = new TransparentPanel(new GridBagLayout()); /** * The constraints used to layout the name bar. */ private final GridBagConstraints nameBarConstraints = new GridBagConstraints(); /** * The label showing the name of the participant. */ private final JLabel nameLabel = new JLabel(); /** * The conference participant which is depicted by this instance. If it is a * Call instance, this instance represents the local peer/user. */ protected final T participant; /** * The panel containing all peer details. */ private final TransparentPanel peerDetailsPanel = new TransparentPanel(); /** * The right details panel. */ private final TransparentPanel rightDetailsPanel = new TransparentPanel(new GridLayout(0, 1)); /** * Security imageID. */ private ImageID securityImageID = ImageLoader.SECURE_OFF_CONF_CALL; /** * The security status of the peer */ protected SecurityStatusLabel securityStatusLabel = null; /** * The component showing the sound level of the participant. */ private SoundLevelIndicator soundIndicator; /** * The status bar of the participant panel. */ private final JPanel statusBar = new TransparentPanel(new GridBagLayout()); /** * The status bar constraints. */ private final GridBagConstraints statusBarConstraints = new GridBagConstraints(); /** * The panel containing the title of the participant. */ private final JPanel titleBar = new CallTitlePanel(new GridBagLayout()); /** * Indicates if video indicator is enabled for this participant. */ private boolean isVideoIndicatorEnabled = false; /** * The image of the participant */ private Image participantImage; /** * Initializes a new <tt>BasicConferenceParticipantPanel</tt> instance which * is to depict a specific conference participant. * * @param callRenderer the renderer for the call * @param participant participant * @param isVideo indicates if we're in a video interface */ public BasicConferenceParticipantPanel( SwingCallRenderer callRenderer, T participant, boolean isVideo) { this.callRenderer = callRenderer; this.participant = participant; this.isVideo = isVideo; constraints.anchor = GridBagConstraints.CENTER; if (isVideo) initVideoConferencePanel(); else initAudioConferencePanel(); } /** * Adds the given <tt>component</tt> to the center below the sound bar. * @param component the component to add */ public void addToCenter(Component component) { rightDetailsPanel.add(component); } /** * Adds the given <tt>component</tt> to the name bar. * @param component the component to add */ public void addToNameBar(Component component) { nameBarConstraints.gridx = nameBarConstraints.gridx + 1; nameBarConstraints.weightx = 0f; nameBarConstraints.insets = new Insets(0, 5, 0, 0); this.nameBar.add(component, nameBarConstraints); } /** * Adds the given <tt>component</tt> to the status bar. * @param component the component to add */ public void addToStatusBar(Component component) { statusBarConstraints.gridx = statusBarConstraints.gridx + 1; statusBarConstraints.weightx = 0f; statusBarConstraints.insets = new Insets(0, 5, 0, 5); this.statusBar.add(component, statusBarConstraints); } /** * Gets the <tt>CallPanel</tt> which contains this instances and uses it to * depict the associated conference participant. * * @return the <tt>CallPanel</tt> which contains this instances and uses it * to depict the associated conference participant */ public CallPanel getCallPanel() { return getCallRenderer().getCallContainer(); } /** * Gets the <tt>CallRenderer</tt> which (directly or indirectly) initialized * this instance and which uses it to depict the associated conference * participant. * * @return the <tt>CallRenderer</tt> which (directly or indirectly) * initialized this instance and which uses it to depict the associated * conference participant */ public SwingCallRenderer getCallRenderer() { return callRenderer; } /** * Gets the conference participant depicted by this instance. * * @return the conference participant depicted by this instance */ public T getParticipant() { return participant; } /** * Gets the name of the participant. * @return returns the name of the participant */ public String getParticipantName() { return nameLabel.getText(); } private void initAudioConferencePanel() { soundIndicator = new SoundLevelIndicator( SoundLevelChangeEvent.MIN_LEVEL, SoundLevelChangeEvent.MAX_LEVEL); soundIndicator.setPreferredSize(new Dimension(80, 30)); this.setLayout(new GridBagLayout()); this.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); this.initTitleBar(); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.gridx = 0; constraints.gridy = 0; constraints.weightx = 1; constraints.weighty = 0; constraints.insets = new Insets(0, 0, 0, 0); this.add(titleBar, constraints); this.initPeerDetailsPanel(); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.gridx = 0; constraints.gridy = 1; constraints.weightx = 1; constraints.weighty = 0; constraints.insets = new Insets(0, 0, 0, 0); this.add(peerDetailsPanel, constraints); } /** * Initializes the details panel for the peer. */ private void initPeerDetailsPanel() { peerDetailsPanel.setLayout(new GridBagLayout()); peerDetailsPanel.setBackground(new Color(255, 255, 255)); setParticipantIcon(null, false); constraints.fill = GridBagConstraints.NONE; constraints.gridx = 0; constraints.gridy = 0; constraints.weightx = 0; constraints.weighty = 0; constraints.insets = new Insets(5, 8, 5, 0); peerDetailsPanel.add(imageLabel, constraints); constraints.fill = GridBagConstraints.BOTH; constraints.gridx = 1; constraints.gridy = 0; constraints.weightx = 1f; constraints.weighty = 0; constraints.insets = new Insets(5, 10, 5, 10); rightDetailsPanel.add(soundIndicator); peerDetailsPanel.add(rightDetailsPanel, constraints); } /** * Creates <tt>SecurityStatusLabel</tt> and adds it to status bar. */ public void initSecurityStatusLabel() { securityStatusLabel = new SecurityStatusLabel(); securityStatusLabel.setSecurityOff(); addToStatusBar(securityStatusLabel); } /** * Initializes the title bar. */ private void initTitleBar() { titleBar.setBorder(BorderFactory.createEmptyBorder(2, 8, 2, 8)); nameLabel.setForeground(Color.WHITE); nameBar.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); nameBarConstraints.gridx = 0; nameBarConstraints.gridy = 0; nameBarConstraints.weightx = 1f; nameBar.add(nameLabel, nameBarConstraints); statusBar.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); callStatusLabel.setForeground(Color.WHITE); statusBarConstraints.gridx = 0; statusBarConstraints.gridy = 0; statusBarConstraints.weightx = 1f; statusBar.add(callStatusLabel, statusBarConstraints); GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 0; constraints.weightx = 1f; constraints.anchor = GridBagConstraints.WEST; titleBar.add(nameBar, constraints); constraints.gridx = 1; constraints.gridy = 0; constraints.weightx = 1f; constraints.anchor = GridBagConstraints.EAST; titleBar.add(statusBar, constraints); } /** * Initializes video conference specific panel. */ private void initVideoConferencePanel() { this.setLayout(new GridBagLayout()); this.setBorder(BorderFactory.createEmptyBorder(7, 7, 7, 7)); this.initTitleBar(); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.gridx = 0; constraints.gridy = 0; constraints.weightx = 1; constraints.weighty = 0; constraints.insets = new Insets(0, 0, 0, 0); this.add(titleBar, constraints); } /** * Reloads default avatar icon. */ public void loadSkin() { if(!iconChanged) { setParticipantIcon(null, false); } if(securityStatusLabel != null) { securityStatusLabel.setIcon( new ImageIcon(ImageLoader.getImage(securityImageID))); } } /** * Overrides {@link JComponent#paintComponent(Graphics)} in order to * customize the background of this panel. * * @param g the <tt>Graphics</tt> object used for painting */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); // If we're in a video interface, we have nothing more to do here. if (isVideo) return; g = g.create(); try { AntialiasingManager.activateAntialiasing(g); g.setColor(bgColor); g.fillRoundRect( 5, 5, this.getWidth() - 10, this.getHeight() - 10, 10, 10); } finally { g.dispose(); } } /** * Indicates that the security has gone off. * * @param evt Details about the event that caused this message. */ public void securityOff(CallPeerSecurityOffEvent evt) { if(securityStatusLabel == null) return; if (evt.getSessionType() == CallPeerSecurityStatusEvent.AUDIO_SESSION) { securityStatusLabel.setText(""); securityStatusLabel.setSecurityOff(); if (securityStatusLabel.getBorder() == null) securityStatusLabel.setBorder( BorderFactory.createEmptyBorder(2, 5, 2, 3)); } } /** * Indicates that the security is turned on. * <p> * Sets the secured status icon to the status panel and initializes/updates * the corresponding security details. * * @param evt Details about the event that caused this message. */ public void securityOn(CallPeerSecurityOnEvent evt) { if(securityStatusLabel == null) return; // If the securityOn is called without a specific event, we'll just set // the security label status to on. if (evt == null) { securityStatusLabel.setSecurityOn(); return; } SrtpControl srtpControl = evt.getSecurityController(); if (!srtpControl.requiresSecureSignalingTransport() || getCallRenderer() .getCallContainer() .getCallConference() .getCalls() .get(0) .getProtocolProvider() .isSignalingTransportSecure()) { if (srtpControl instanceof ZrtpControl) { securityStatusLabel.setText("zrtp"); if (!((ZrtpControl) srtpControl).isSecurityVerified()) securityStatusLabel.setSecurityPending(); else securityStatusLabel.setSecurityOn(); } else securityStatusLabel.setSecurityOn(); } } /** * Indicates that the security status is pending confirmation. */ public void securityPending() { securityStatusLabel.setSecurityPending(); } /** * Sets the reason of a call failure if one occurs. The renderer should * display this reason to the user. * @param reason the reason to display */ protected void setErrorReason(final String reason) { if(!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { setErrorReason(reason); } }); return; } if (errorMessageComponent == null) { errorMessageComponent = new JTextPane(); JTextPane textPane = (JTextPane) errorMessageComponent; textPane.setEditable(false); textPane.setOpaque(false); StyledDocument doc = textPane.getStyledDocument(); MutableAttributeSet standard = new SimpleAttributeSet(); StyleConstants.setFontFamily(standard, textPane.getFont().getFamily()); StyleConstants.setFontSize(standard, 12); doc.setParagraphAttributes(0, 0, standard, true); addToCenter(errorMessageComponent); this.revalidate(); } errorMessageComponent.setText(reason); if (isVisible()) { Window parentWindow = SwingUtilities.getWindowAncestor(errorMessageComponent); if (parentWindow != null) parentWindow.pack(); errorMessageComponent.repaint(); } } /** * Sets the image of the participant. * @param image the image to set */ public void setParticipantImage(byte[] image) { setParticipantIcon(image, true); } /** * Sets the name of the participant. * @param participantName the name of the participant */ public void setParticipantName(String participantName) { nameLabel.setText(participantName); } /** * Sets the state of the participant. * @param participantState the state of the participant */ public void setParticipantState(final String participantState) { if(!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { setParticipantState(participantState); } }); return; } callStatusLabel.setText(participantState.toLowerCase()); } /** * Sets the background color of the title panel. * @param color the background color to set */ protected void setTitleBackground(Color color) { titleBar.setBackground(color); } /** * Updates the sound level bar to reflect the new sound level value. * @param soundLevel the new sound level value */ public void updateSoundBar(int soundLevel) { if (soundIndicator != null) soundIndicator.updateSoundLevel(soundLevel); } /** * Enables or disabled video indicator in this conference participant * panel. * * @param enable <tt>true</tt> to enable video indicator, <tt>false</tt> - * otherwise */ public void enableVideoIndicator(boolean enable) { isVideoIndicatorEnabled = enable; setParticipantIcon(null, true); } /** * Sets the participant icon. * * @param image the image to set as an icon. If null will use the last set * image or the default one * @param changed indicates if this is a change of the icon */ private void setParticipantIcon(byte[] image, boolean changed) { if (image != null && image.length > 0) participantImage = ImageUtils.getScaledRoundedIcon( image, AVATAR_WIDTH, AVATAR_HEIGHT).getImage(); else if (participantImage == null) participantImage = ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO) .getScaledInstance( AVATAR_WIDTH, AVATAR_HEIGHT, Image.SCALE_SMOOTH); Image videoIndicatorImage = null; if (isVideoIndicatorEnabled) videoIndicatorImage = ImageLoader.getImage(ImageLoader.CONFERENCE_VIDEO_INDICATOR); Icon avatarIcon = null; if (videoIndicatorImage != null && participantImage != null) avatarIcon = new ImageIcon(ImageLoader.getImage( participantImage, videoIndicatorImage, participantImage.getWidth(null) - videoIndicatorImage.getWidth(null) + 5, participantImage.getHeight(null) - videoIndicatorImage.getHeight(null) + 5)); else if (participantImage != null) avatarIcon = new ImageIcon(participantImage); if (avatarIcon != null) { imageLabel.setIcon(avatarIcon); if (changed) iconChanged = true; } } }