/*
* 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());
}
}
}
}