/* * 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 java.util.*; import javax.swing.*; import net.java.sip.communicator.impl.gui.main.call.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.skin.*; /** * Depicts a specific <tt>CallPeer</tt> who is a focus of a telephony conference * and the <tt>ConferenceMember</tt>s whom the specified <tt>CallPeer</tt> is * acting as a conference focus of. * * @author Yana Stamcheva * @author Lyubomir Marinov * @author Hristo Terezov */ public class ConferenceFocusPanel extends TransparentPanel implements ConferenceCallPeerRenderer, Skinnable { /** * Serial version UID. */ private static final long serialVersionUID = 0L; /** * The <tt>BasicConferenceCallPanel</tt> which initialized this instance and * which uses it to depict {@link #focusPeer}. */ private final BasicConferenceCallPanel callRenderer; /* * XXX The conferenceMemberPanels field is modified by various threads * without any synchronization whatsoever. */ /** * The <tt>ConferenceMemberPanel</tt>s which depict the * <tt>ConferenceMember</tt>s of {@link #focusPeer}. Mapped by their * respective <tt>ConferenceMember</tt> instances for optimized access. */ private final Map<ConferenceMember, ConferenceMemberPanel> conferenceMemberPanels = new Hashtable<ConferenceMember, ConferenceMemberPanel>(); private final GridBagConstraints cnstrnts; /** * The <tt>CallPeer</tt> depicted by this instance. */ private final CallPeer focusPeer; private final FocusPeerListener focusPeerListener = new FocusPeerListener(); /** * The <tt>ConferencePeerPanel</tt> which depicts {@link #focusPeer} without * the <tt>ConferenceMember</tt>s which participate in the telephony * conference that it is the focus of. */ private ConferencePeerPanel focusPeerPanel; /** * Initializes a new <tt>ConferenceFocusPanel</tt> which is to depict a * specific <tt>CallPeer</tt> on behalf of a specific * <tt>BasicConferenceCallPanel</tt> i.e. <tt>CallRenderer</tt>. * * @param callRenderer the <tt>BasicConferenceCallPanel</tt> which requests * the initialization of the new instance and which will use the new * instance to depict the specified <tt>CallPeer</tt> * @param callPeer the <tt>CallPeer</tt> to be depicted by the new instance */ public ConferenceFocusPanel( BasicConferenceCallPanel callRenderer, CallPeer callPeer) { super(new GridBagLayout()); this.focusPeer = callPeer; this.callRenderer = callRenderer; cnstrnts = new GridBagConstraints(); cnstrnts.fill = GridBagConstraints.BOTH; cnstrnts.gridx = 0; cnstrnts.gridy = 0; cnstrnts.insets = new Insets(0, 0, 3, 0); cnstrnts.weightx = 1; cnstrnts.weighty = 0; /* * Add the user interface which will depict the focusPeer without the * ConferenceMembers. */ addFocusPeerPanel(); this.focusPeer.addCallPeerConferenceListener(focusPeerListener); if (ConferencePeerPanel.isSoundLevelIndicatorEnabled()) { this.focusPeer.addConferenceMembersSoundLevelListener( focusPeerListener); } for (ConferenceMember conferenceMember : this.focusPeer.getConferenceMembers()) { addConferenceMemberPanel(conferenceMember); } } /** * Adds a <tt>ConferenceMemberPanel</tt> to depict a specific * <tt>ConferenceMember</tt> if there is not one yet. * * @param conferenceMember the <tt>ConferenceMember</tt> to be depicted */ private void addConferenceMemberPanel(ConferenceMember conferenceMember) { /* * The local user/peer should not be depicted by a ConferenceMemberPanel * because it is not the responsibility of ConferenceFocusPanel and * ConferenceMemberPanel. */ if (CallManager.isLocalUser(conferenceMember)) return; /* * If the focusPeer is reported as a ConferenceMember, it should be * depicted by focusPeerPanel only and not a ConferenceMemberPanel. */ if (CallManager.addressesAreEqual( conferenceMember.getAddress(), focusPeer.getAddress())) return; /* * The specified ConferenceMember is already depicted by this view with * a ConferenceMemberPanel. */ if (conferenceMemberPanels.containsKey(conferenceMember)) return; ConferenceMemberPanel conferenceMemberPanel = new ConferenceMemberPanel(callRenderer, conferenceMember, false); /* * Remember the ConferenceMemberPanel which depicts the specified * ConferenceMember. */ conferenceMemberPanels.put(conferenceMember, conferenceMemberPanel); /* * Add the newly-initialized ConferenceMemberPanel to the user interface * hierarchy of this view. */ add(conferenceMemberPanel, cnstrnts); cnstrnts.gridy++; initSecuritySettings(); packWindow(); } /** * Adds the <tt>ConferencePeerPanel</tt> which will depict * {@link #focusPeer} without the <tt>ConferenceMember</tt>s which * participate in the telephony conference that it is the focus of. */ private void addFocusPeerPanel() { focusPeerPanel = new ConferencePeerPanel(callRenderer, focusPeer); add(focusPeerPanel, cnstrnts); cnstrnts.gridy++; packWindow(); } /** * Resizes the window to fit the layout. */ private void packWindow() { Window window = SwingUtilities.getWindowAncestor(this); if (window != null) window.pack(); } /** * Releases the resources acquired by this instance which require explicit * disposal (e.g. any listeners added to the depicted <tt>CallPeer</tt>. * Invoked by <tt>BasicConferenceCallPanel</tt> when it determines that this * <tt>ConferenceFocusPanel</tt> is no longer necessary. */ public void dispose() { focusPeer.removeCallPeerConferenceListener(focusPeerListener); focusPeer.removeConferenceMembersSoundLevelListener(focusPeerListener); if (focusPeerPanel != null) focusPeerPanel.dispose(); for (ConferenceMemberPanel conferenceMemberPanel : conferenceMemberPanels.values()) { conferenceMemberPanel.dispose(); } } /** * Returns the parent <tt>CallPanel</tt> containing this renderer. * * @return the parent <tt>CallPanel</tt> containing this renderer */ public CallPanel getCallPanel() { return getCallRenderer().getCallContainer(); } /** * Returns the parent call renderer. * * @return the parent call renderer */ public SwingCallRenderer getCallRenderer() { return callRenderer; } /** * Returns the component associated with this renderer. * * @return the component associated with this renderer */ public Component getComponent() { return this; } /** * Initializes the security settings of {@link #focusPeerPanel} and * {@link #conferenceMemberPanels} with the security settings of * {@link #focusPeer}. */ private void initSecuritySettings() { CallPeerSecurityStatusEvent securityEvent = focusPeer.getCurrentSecuritySettings(); if (securityEvent instanceof CallPeerSecurityOnEvent) securityOn((CallPeerSecurityOnEvent) securityEvent); } /** * Indicates if the local video component is currently visible. * * @return <tt>true</tt> if the local video component is currently visible, * <tt>false</tt> - otherwise */ public boolean isLocalVideoVisible() { return focusPeerPanel.isLocalVideoVisible(); } /** * {@inheritDoc} * * The implementation of <tt>ConferenceFocusPanel</tt> does nothing. */ public void loadSkin() { } /** * Notifies this instance about a specific <tt>CallPeerConferenceEvent</tt> * which was fired by {@link #focusPeer}. The notification is brought in the * AWT event dispatching thread. * * @param ev the <tt>CallPeerConferenceEvent</tt> which was fired by * {@link #focusPeer} and which this instance is notified about */ protected void onCallPeerConferenceEvent(final CallPeerConferenceEvent ev) { /* * ConferenceFocusPanel is interested in the additions and the removals * of ConferenceMembers only. Because we are handling the * CallPeerConferenceEvents in the AWT event dispatching thread which is * not friendly to the garbage collection, filter out the * CallPeerConferenceEvents which are of no interest at this time. */ switch(ev.getEventID()) { case CallPeerConferenceEvent.CONFERENCE_MEMBER_ADDED: case CallPeerConferenceEvent.CONFERENCE_MEMBER_REMOVED: if (SwingUtilities.isEventDispatchThread()) { onCallPeerConferenceEventInEventDispatchThread(ev); } else { SwingUtilities.invokeLater( new Runnable() { public void run() { onCallPeerConferenceEventInEventDispatchThread( ev); } }); } break; default: /* * As it is said above, we are not interested in all * CallPeerConferenceEvents. */ break; } } /** * Notifies this instance about a specific <tt>CallPeerConferenceEvent</tt> * which was fired by {@link #focusPeer}. * * @param ev the <tt>CallPeerConferenceEvent</tt> which was fired by * {@link #focusPeer} and which this instance is notified about */ protected void onCallPeerConferenceEventInEventDispatchThread( CallPeerConferenceEvent ev) { ConferenceMember conferenceMember = ev.getConferenceMember(); switch (ev.getEventID()) { case CallPeerConferenceEvent.CONFERENCE_MEMBER_ADDED: addConferenceMemberPanel(conferenceMember); break; case CallPeerConferenceEvent.CONFERENCE_MEMBER_REMOVED: removeConferenceMemberPanel(conferenceMember); break; } } /** * 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); g = g.create(); try { AntialiasingManager.activateAntialiasing(g); int height = getHeight(); int width = getWidth(); g.setColor(Color.LIGHT_GRAY); g.fillRect(0, 0, width, height); g.setColor(Color.DARK_GRAY); g.drawLine(0, 0, width, 0); g.drawLine(0, height - 1, width, height - 1); } finally { g.dispose(); } } /** * Prints the given DTMG character through this <tt>CallPeerRenderer</tt>. * * @param dtmfChar the DTMF char to print */ public void printDTMFTone(char dtmfChar) { focusPeerPanel.printDTMFTone(dtmfChar); } /** * Removes the <tt>ConferenceMemberPanel</tt> depicting a specific * <tt>ConferenceMember</tt>. * * @param conferenceMember the <tt>ConferenceMember</tt> whose depicting * <tt>ConferenceMemberPanel</tt> is to be removed */ private void removeConferenceMemberPanel(ConferenceMember conferenceMember) { ConferenceMemberPanel conferenceMemberPanel = conferenceMemberPanels.remove(conferenceMember); if (conferenceMemberPanel != null) { remove(conferenceMemberPanel); conferenceMemberPanel.dispose(); packWindow(); } } /** * The handler for the security event received. The security event * for starting establish a secure connection. * * @param securityNegotiationStartedEvent * the security started event received */ public void securityNegotiationStarted( CallPeerSecurityNegotiationStartedEvent securityNegotiationStartedEvent) {} /** * Indicates that the security is turned off. * * @param evt Details about the event that caused this message. */ public void securityOff(CallPeerSecurityOffEvent evt) { focusPeerPanel.securityOff(evt); for (ConferenceMemberPanel member : conferenceMemberPanels.values()) member.securityOff(evt); } /** * Indicates that the security is turned on. * * @param evt Details about the event that caused this message. */ public void securityOn(CallPeerSecurityOnEvent evt) { focusPeerPanel.securityOn(evt); for (ConferenceMemberPanel member : conferenceMemberPanels.values()) member.securityOn(evt); } /** * Indicates that the security status is pending confirmation. */ public void securityPending() { focusPeerPanel.securityPending(); } /** * Indicates that the security is timeouted, is not supported by the * other end. * @param evt Details about the event that caused this message. */ public void securityTimeout(CallPeerSecurityTimeoutEvent evt) { } /** * Sets the reason of a call failure if one occurs. The renderer should * display this reason to the user. * * @param reason the reason of the error to set */ public void setErrorReason(String reason) { focusPeerPanel.setErrorReason(reason); } /** * Shows/hides the local video component. * * @param isVisible <tt>true</tt> to show the local video, <tt>false</tt> - * otherwise */ public void setLocalVideoVisible(boolean isVisible) { focusPeerPanel.setLocalVideoVisible(isVisible); } /** * Sets the mute property value. * * @param isMute indicates if the call with this peer is * muted */ public void setMute(boolean isMute) { focusPeerPanel.setMute(isMute); } /** * Sets the "on hold" property value. * * @param isOnHold indicates if the call with this peer is put on hold */ public void setOnHold(boolean isOnHold) { focusPeerPanel.setOnHold(isOnHold); } /** * Sets the <tt>image</tt> of the peer. * * @param image the image to set */ public void setPeerImage(byte[] image) { focusPeerPanel.setPeerImage(image); } /** * Sets the name of the peer. * * @param name the name of the peer */ public void setPeerName(String name) { focusPeerPanel.setPeerName(name); } /** * Sets the state of the contained call peer by specifying the * state name. * * @param oldState the previous state of the peer * @param newState the new state of the peer * @param stateString the state of the contained call peer */ public void setPeerState( CallPeerState oldState, CallPeerState newState, String stateString) { focusPeerPanel.setPeerState(oldState, newState, stateString); } /** * {@inheritDoc} * * The implementation of <tt>ConferenceFocusPanel</tt> does nothing. */ public void setSecurityPanelVisible(boolean visible) { } /** * Enables or disabled video indicator in this conference participant * panel. * * @param confMember the <tt>ConferenceMember</tt>, which video indicator * we'd like to update * @param enable <tt>true</tt> to enable video indicator, <tt>false</tt> - * otherwise */ public void enableVideoIndicator( ConferenceMember confMember, boolean enable) { if (!conferenceMemberPanels.containsKey(confMember)) return; ConferenceMemberPanel confMemberPanel = conferenceMemberPanels.get(confMember); confMemberPanel.enableVideoIndicator(enable); } /** * Enables or disabled video indicator of the <tt>focusPeerPanel</tt>. * * @param enable <tt>true</tt> to enable video indicator, <tt>false</tt> - * otherwise */ public void enableVideoIndicator(boolean enable) { focusPeerPanel.enableVideoIndicator(enable); } /** * Implements the listeners which get notified about events related to the * <tt>CallPeer</tt> depicted by this <tt>ConferenceFocusPanel</tt> and * which may cause a need to update this view from its model. */ private class FocusPeerListener extends CallPeerConferenceAdapter implements ConferenceMembersSoundLevelListener { /** * {@inheritDoc} */ @Override protected void onCallPeerConferenceEvent(CallPeerConferenceEvent ev) { ConferenceFocusPanel.this.onCallPeerConferenceEvent(ev); } /** * {@inheritDoc} * * Notifies this listener about changes in the audio/sound level-related * information of the <tt>ConferenceMember</tt>s of * {@link ConferenceFocusPanel#focusPeer}. Updates the user interface * which displays the audio/sound levels i.e. * <tt>SoundLevelIndicator</tt>. */ public void soundLevelChanged(ConferenceMembersSoundLevelEvent ev) { Map<ConferenceMember, Integer> levels = ev.getLevels(); // focusPeerPanel String address = focusPeerPanel.getCallPeerContactAddress(); for(Map.Entry<ConferenceMember, Integer> e : levels.entrySet()) { ConferenceMember key = e.getKey(); Integer value = e.getValue(); if(CallManager.addressesAreEqual(key.getAddress(), address)) { focusPeerPanel.updateSoundBar(value); break; } } // conferenceMemberPanels for (Map.Entry<ConferenceMember, ConferenceMemberPanel> entry : conferenceMemberPanels.entrySet()) { ConferenceMember member = entry.getKey(); Integer memberSoundLevel = levels.get(member); entry.getValue().updateSoundBar( (memberSoundLevel == null) ? 0 : memberSoundLevel.intValue()); } } } }