/*
* 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;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.plugin.desktoputil.*;
/**
* The dialog created for a given call.
*
* @author Yana Stamcheva
* @author Adam Netocny
* @author Lyubomir Marinov
*/
public class CallDialog
extends SIPCommFrame
implements CallContainer,
CallTitleListener
{
/**
* Serial version UID.
*/
private static final long serialVersionUID = 0L;
/**
* Enabling force minimized mode will always open call dialog minimized.
* The call dialog still can be shown, but by default it will be minimized.
*/
private static final String FORCE_MINIMIZED_MODE
= "net.java.sip.communicator.impl.gui.main.call.FORCE_MINIMIZED_MODE";
/**
* Finds a <tt>Container</tt> which is an ancestor of a specific
* <tt>Component</tt>, has a set <tt>preferredSize</tt> and is closest to
* the specified <tt>Component</tt> up the ancestor hierarchy.
*
* @param component the <tt>Component</tt> whose ancestor hierarchy is to be
* searched upwards
* @return a <tt>Container</tt>, if any, which is an ancestor of the
* specified <tt>component</tt>, has a set <tt>preferredSize</tt> and is
* closest to the specified <tt>component</tt> up the ancestor hierarchy
*/
private static Container findClosestAncestorWithSetPreferredSize(
Component component)
{
if ((component instanceof Container) && component.isPreferredSizeSet())
return (Container) component;
else
{
Container parent;
while ((parent = component.getParent()) != null)
{
if (parent.isPreferredSizeSet())
return parent;
else
component = parent;
}
return null;
}
}
/**
* The panel, where all call components are added.
*/
private CallPanel callPanel;
private final WindowStateListener windowStateListener
= new WindowStateListener()
{
public void windowStateChanged(WindowEvent ev)
{
switch (ev.getID())
{
case WindowEvent.WINDOW_DEACTIVATED:
case WindowEvent.WINDOW_ICONIFIED:
case WindowEvent.WINDOW_LOST_FOCUS:
setFullScreen(false);
break;
}
}
};
/**
* Creates a <tt>CallDialog</tt> by specifying the underlying call panel.
*/
public CallDialog()
{
super(true, false);
setMinimumSize(new Dimension(360, 300));
}
/**
* Adds a call panel.
*
* @param callPanel the call panel to add to this dialog
*/
public void addCallPanel(CallPanel callPanel)
{
this.callPanel = callPanel;
getContentPane().add(callPanel);
callPanel.addCallTitleListener(this);
setTitle(callPanel.getCallTitle());
if (!isVisible())
{
pack();
// checks whether we need to open the call dialog in minimized mode
if(GuiActivator.getConfigurationService()
.getBoolean(FORCE_MINIMIZED_MODE, false))
{
setState(ICONIFIED);
}
setVisible(true);
}
}
/**
* Called when the title of the given <tt>CallPanel</tt> changes.
*
* @param callPanel the <tt>CallPanel</tt>, which title has changed
*/
public void callTitleChanged(CallPanel callPanel)
{
if (this.callPanel.equals(callPanel))
setTitle(callPanel.getCallTitle());
}
/**
* {@inheritDoc}
*
* Hang ups the call/telephony conference depicted by this
* <tt>CallDialog</tt> on close.
*/
@Override
protected void close(boolean escape)
{
if (escape)
{
/*
* In full-screen mode, ESC does not close this CallDialog but exits
* from full-screen to windowed mode.
*/
if (isFullScreen())
{
setFullScreen(false);
return;
}
}
else
{
/*
* If the window has been closed by clicking the X button or
* pressing the key combination corresponding to the same button we
* close the window first and then perform all hang up operations.
*/
callPanel.disposeCallInfoFrame();
// We hide the window here. It will be disposed when the call has
// been ended.
setVisible(false);
}
// Then perform hang up operations.
callPanel.actionPerformedOnHangupButton(escape);
}
/**
* {@inheritDoc}
*
* The delay implemented by <tt>CallDialog</tt> is 5 seconds.
*/
public void close(final CallPanel callPanel, boolean delay)
{
if (this.callPanel.equals(callPanel))
{
if (delay)
{
Timer timer
= new Timer(
5000,
new ActionListener()
{
public void actionPerformed(ActionEvent ev)
{
dispose();
}
});
timer.setRepeats(false);
timer.start();
}
else
{
dispose();
}
}
}
/**
* {@inheritDoc}
*
* <tt>CallDialog</tt> prepares the <tt>CallPanel</tt> it contains for
* garbage collection.
*/
@Override
public void dispose()
{
super.dispose();
/*
* Technically, CallManager adds/removes the callPanel to/from this
* instance. It may want to just move it to another CallContainer so it
* does not sound right that we are disposing of it. But we do not have
* such a case at this time so try to reduce the risk of memory leaks.
*/
if (this.callPanel != null)
{
callPanel.disposeCallInfoFrame();
callPanel.dispose();
}
}
/**
* {@inheritDoc}
*
* Attempts to adjust the size of this <tt>Frame</tt> as requested in the
* AWT event dispatching thread.
* <p>
* The method may be executed on the AWT event dispatching thread only
* because whoever is making the decision to request an adjustment of the
* Frame size in relation to a AWT Component should be analyzing that same
* Component in the AWT event dispatching thread only.
* </p>
*
* @throws RuntimeException if the method is not called on the AWT event
* dispatching thread
*/
public void ensureSize(Component component, int width, int height)
{
CallManager.assertIsEventDispatchingThread();
Frame frame = CallPeerRendererUtils.getFrame(component);
if (frame == null)
return;
else if ((frame.getExtendedState() & Frame.MAXIMIZED_BOTH)
== Frame.MAXIMIZED_BOTH)
{
/*
* Forcing the size of a Component which is displayed in a maximized
* window does not sound like anything we want to do.
*/
return;
}
else if (frame.equals(
frame.getGraphicsConfiguration().getDevice()
.getFullScreenWindow()))
{
/*
* Forcing the size of a Component which is displayed in a
* full-screen window does not sound like anything we want to do.
*/
return;
}
else if (!frame.equals(this))
{
/* This Frame will try to adjust only its own size. */
return;
}
else if ((component.getHeight() >= height)
&& (component.getWidth() >= width))
{
/*
* We will only enlarge the frame size. If the component has already
* been given at least what it is requesting, do not enlarge the
* frame size because the whole calculation is prone to inaccuracy.
*/
return;
}
else
{
/*
* If there is no callPanel, it is unlikely that this CallDialog
* will be asked to ensureSize. Anyway, support the scenario just in
* case. In light of the absence of a callPanel to guide this
* CallDialog about the preferred size, we do not have much of a
* choice but to trust the method arguments.
*/
if (callPanel != null)
{
/*
* If there is a callPanel, we are likely to get a much better
* estimation about the preferred size by asking the callPanel
* rather than by trusting the method arguments. For example,
* the visual Component displaying the video streaming from the
* local user/peer to the remote peer(s) will think that its
* preferred size is the one to base this Frame's size on but
* that may be misleading because the local video may not be
* displayed with its preferred size even if this Frame's size
* will accommodate it.
*/
/*
* Just asking the callPanel about its preferredSize would've
* been terrificly great. Unfortunately, that is presently
* futile because the callPanel may have a preferredSize while
* we are still required to display visual Components displaying
* video in their non-scaled size. The same goes for any
* Container which is an ancestor of the specified component.
*/
Container ancestor
= findClosestAncestorWithSetPreferredSize(component);
if (ancestor == null)
ancestor = callPanel;
/*
* If the ancestor has a forced preferredSize, its LayoutManager
* may be able to give a good enough estimation.
*/
if (ancestor.isPreferredSizeSet())
{
LayoutManager ancestorLayout = ancestor.getLayout();
if (ancestorLayout != null)
{
Dimension preferredLayoutSize
= ancestorLayout.preferredLayoutSize(ancestor);
if (preferredLayoutSize != null)
{
component = ancestor;
width = preferredLayoutSize.width;
height = preferredLayoutSize.height;
}
}
}
else
{
/*
* If the ancestor doesn't have a preferredSize forced, then
* we may think that it will calculate an appropriate
* preferredSize itself.
*/
Dimension prefSize = ancestor.getPreferredSize();
if (prefSize != null)
{
component = ancestor;
width = prefSize.width;
height = prefSize.height;
}
}
}
/*
* If the component (which may be an ancestor of the Component
* specified as an argument to the ensureSize method at this point)
* has not been given a size, we will make a mistake if we try to
* use it for the purposes of determining how much this Frame is to
* be enlarged.
*/
Dimension componentSize = component.getSize();
if ((componentSize.width < 1) || (componentSize.height < 1))
return;
Dimension frameSize = frame.getSize();
int newFrameWidth = frameSize.width + width - componentSize.width;
int newFrameHeight
= frameSize.height + height - componentSize.height;
// Respect the minimum size.
Dimension minSize = frame.getMinimumSize();
if (newFrameWidth < minSize.width)
newFrameWidth = minSize.width;
if (newFrameHeight < minSize.height)
newFrameHeight = minSize.height;
// Don't get bigger than the screen.
Rectangle screenBounds
= frame.getGraphicsConfiguration().getBounds();
if (newFrameWidth > screenBounds.width)
newFrameWidth = screenBounds.width;
if (newFrameHeight > screenBounds.height)
newFrameHeight = screenBounds.height;
/*
* If we're going to make too small a change, don't even bother.
* Besides, we don't want some weird recursive resizing.
* Additionally, do not reduce the Frame size.
*/
boolean changeWidth = ((newFrameWidth - frameSize.width) > 1);
boolean changeHeight = ((newFrameHeight - frameSize.height) > 1);
if (changeWidth || changeHeight)
{
if (!changeWidth)
newFrameWidth = frameSize.width;
else if (!changeHeight)
newFrameHeight = frameSize.height;
/*
* The latest requirement with respect to the behavior upon
* resizing is to center the Frame.
*/
int newFrameX
= screenBounds.x
+ (screenBounds.width - newFrameWidth) / 2;
int newFrameY
= screenBounds.y
+ (screenBounds.height - newFrameHeight) / 2;
// Do not let the top left go out of the screen.
if (newFrameX < screenBounds.x)
newFrameX = screenBounds.x;
if (newFrameY < screenBounds.y)
newFrameY = screenBounds.y;
frame.setBounds(
newFrameX, newFrameY,
newFrameWidth, newFrameHeight);
/*
* Make sure that the component which originally requested the
* update to the size of the frame realizes the change as soon
* as possible; otherwise, it may request yet another update.
*/
if (frame.isDisplayable())
{
if (frame.isValid())
frame.doLayout();
else
frame.validate();
frame.repaint();
}
else
frame.doLayout();
}
}
}
/**
* Returns the frame of the call window.
*
* @return the frame of the call window
*/
public JFrame getFrame()
{
return this;
}
/**
* Overrides getMinimumSize and checks the minimum size that
* is needed to display buttons and use it for minimum size if
* needed.
* @return minimum size.
*/
@Override
public Dimension getMinimumSize()
{
Dimension minSize = super.getMinimumSize();
if(callPanel != null)
{
int minButtonWidth = callPanel.getMinimumButtonWidth();
if(minButtonWidth > minSize.getWidth())
minSize = new Dimension(minButtonWidth, 300);
}
return minSize;
}
/**
* Indicates if the given <tt>callPanel</tt> is currently visible.
*
* @param callPanel the <tt>CallPanel</tt>, for which we verify
* @return <tt>true</tt> if the given call container is visible in this
* call window, otherwise - <tt>false</tt>
*/
public boolean isCallVisible(CallPanel callPanel)
{
return this.callPanel.equals(callPanel) ? isVisible() : false;
}
/**
* {@inheritDoc}
*/
public boolean isFullScreen()
{
return isFullScreen(getFrame());
}
/**
* Determines whether a specific <tt>Window</tt> is displayed in full-screen
* mode.
*
* @param window the <tt>Window</tt> to be checked whether it is displayed
* in full-screen mode
* @return <tt>true</tt> if the specified <tt>window</tt> is displayed in
* full-screen mode; otherwise, <tt>false</tt>
*/
public static boolean isFullScreen(Window window)
{
GraphicsConfiguration graphicsConfiguration
= window.getGraphicsConfiguration();
if (graphicsConfiguration != null)
{
GraphicsDevice device = graphicsConfiguration.getDevice();
if (device != null)
return window.equals(device.getFullScreenWindow());
}
return false;
}
/**
* {@inheritDoc}
*/
public void setFullScreen(boolean fullScreen)
{
GraphicsConfiguration graphicsConfiguration
= getGraphicsConfiguration();
if (graphicsConfiguration != null)
{
GraphicsDevice device = graphicsConfiguration.getDevice();
if (device != null)
{
boolean thisIsFullScreen = equals(device.getFullScreenWindow());
boolean firePropertyChange = false;
boolean setVisible = isVisible();
try
{
if (fullScreen)
{
if (!thisIsFullScreen)
{
/*
* XXX The setUndecorated method will only work if
* this Window is not displayable.
*/
windowDispose();
setUndecorated(true);
device.setFullScreenWindow(this);
firePropertyChange = true;
}
}
else if (thisIsFullScreen)
{
/*
* XXX The setUndecorated method will only work if this
* Window is not displayable.
*/
windowDispose();
setUndecorated(false);
device.setFullScreenWindow(null);
firePropertyChange = true;
}
if (firePropertyChange)
{
if (fullScreen)
{
addWindowStateListener(windowStateListener);
/*
* If full-screen mode, a black background is the
* most common.
*/
getContentPane().setBackground(Color.BLACK);
}
else
{
removeWindowStateListener(windowStateListener);
/*
* In windowed mode, a system-defined background is
* the most common.
*/
getContentPane().setBackground(null);
}
firePropertyChange(
PROP_FULL_SCREEN,
thisIsFullScreen,
fullScreen);
}
}
finally
{
/*
* Regardless of whether this Window successfully entered or
* exited full-screen mode, make sure that remains visible.
*/
if (setVisible)
setVisible(true);
}
}
}
}
}