/* * 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.List; 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.*; /** * Extends <tt>BasicConferenceCallPanel</tt> to implement a user interface * <tt>Component</tt> which depicts an audio-only <tt>CallConference</tt> and is * contained in a <tt>CallPanel</tt>. (Even if the <tt>CallConference</tt> * actually has video, <tt>AudioConferenceCallPanel</tt> does not dispay the * video.) * * @author Dilshan Amadoru * @author Yana Stamcheva * @author Lyubomir Marinov */ public class AudioConferenceCallPanel extends BasicConferenceCallPanel { /** * The <tt>minimumSize</tt> to be set on the {@link #scrollPane} of * <tt>AudioConferenceCallPanel</tt>. */ private static final Dimension SCROLL_PANE_MINIMUM_SIZE = new Dimension( 400 /* It sounds reasonable without being justified. */, 100 /* It is arbitrary and, hopefully, unnecessary. */); /** * The <tt>GridBagConstraints</tt> of the <tt>Component</tt>s which depict * the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating * in the telephony conference depicted by this * <tt>AudioConferenceCallPanel</tt>. */ private final GridBagConstraints constraints; /** * The panel which contains ConferencePeerPanels. */ private final TransparentPanel mainPanel; /** * The scroll pane. */ private final JScrollPane scrollPane; /** * The implementation of the routine which scrolls {@link #scrollPane} to * its bottom. */ private final Runnable scrollToBottomRunnable = new Runnable() { public void run() { JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar(); if (verticalScrollBar != null) verticalScrollBar.setValue(verticalScrollBar.getMaximum()); } }; /** * Initializes a new <tt>AudioConferenceCallPanel</tt> instance which is to * be used by a specific <tt>CallPanel</tt> to depict a specific * <tt>CallConference</tt>. The new instance will depict only the * audio-related information and will ignore the video-related information * should there be any. * * @param callPanel the <tt>CallPanel</tt> which will use the new instance * to depict the specified <tt>CallConference</tt>. * @param callConference the <tt>CallConference</tt> to be depicted by the * new instance */ public AudioConferenceCallPanel( CallPanel callPanel, CallConference callConference) { super(callPanel, callConference); mainPanel = new TransparentPanel(); mainPanel.setLayout(new GridBagLayout()); scrollPane = new JScrollPane(); scrollPane.setHorizontalScrollBarPolicy( JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); // Temporarily disables the custom viewport, as we believe the issues it // was fixing are now fixed by calling pack() on the parent window. // scrollPane.setViewport(new MyViewport()); scrollPane.setViewportView(mainPanel); scrollPane.setOpaque(false); scrollPane.getViewport().setOpaque(false); scrollPane.setBorder(null); scrollPane.setMinimumSize(SCROLL_PANE_MINIMUM_SIZE); mainPanel.setTransferHandler(new CallTransferHandler(callConference)); /* * XXX Call addCallPeerPanel(CallPeer) after calling addVideoContainer() * because the video may already be flowing between the CallPeers. * Otherwise, the videos of the remote CallPeers will not be shown. */ add(scrollPane, BorderLayout.CENTER); constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.BOTH; constraints.gridx = 0; constraints.weightx = 1; constraints.weighty = 0; constraints.insets = new Insets(0, 0, 3, 0); /* * Notify the super that this instance has completed its initialization * and the view that it implements is ready to be updated from the * model. */ initializeComplete(); } /** * Returns the vertical <tt>JScrollBar</tt>. * * @return the vertical <tt>JScrollBar</tt> */ public JScrollBar getVerticalScrollBar() { return (scrollPane == null) ? null : scrollPane.getVerticalScrollBar(); } /** * {@inheritDoc} * * Makes sure that the <tt>CallPeer</tt>s which are conference focuses are * depicted by <tt>ConferenceFocusPanel</tt>s and the <tt>CallPeer</tt>s * which are not conference focuses are depicted by * <tt>ConferencePeerPanel</tt>s. */ @Override protected ConferenceCallPeerRenderer updateViewFromModel( ConferenceCallPeerRenderer callPeerPanel, CallPeer callPeer) { if (callPeer == null) { List<Call> calls = callConference.getCalls(); Call call = calls.isEmpty() ? null : calls.get(0); if (callPeerPanel instanceof ConferencePeerPanel) { if (!((ConferencePeerPanel) callPeerPanel).getCall().equals( call)) callPeerPanel = null; } else callPeerPanel = null; if ((callPeerPanel == null) && (call != null)) callPeerPanel = new ConferencePeerPanel(this, call, false); } else { if (callPeer.isConferenceFocus() || (callPeer.getConferenceMemberCount() > 0)) { if (!(callPeerPanel instanceof ConferenceFocusPanel)) { callPeerPanel = new ConferenceFocusPanel(this, callPeer); } } else if (!(callPeerPanel instanceof ConferencePeerPanel)) { callPeerPanel = new ConferencePeerPanel(this, callPeer); } } return callPeerPanel; } /** * {@inheritDoc} */ @Override protected void viewForModelAdded( ConferenceCallPeerRenderer callPeerPanel, CallPeer callPeer) { /* * Add the Component which is widget equivalent to the specified * callPeerPanel to the user interface hierarchy of this instance. */ constraints.gridy = (callPeer == null) ? 0 : (constraints.gridy + 1); mainPanel.add(callPeerPanel.getComponent(), constraints); SwingUtilities.invokeLater(scrollToBottomRunnable); // If the parent window exists already try to adjust the size of the // window to the new content. Fixes cut off conference window. Window parentWindow = SwingUtilities.getWindowAncestor(mainPanel); if (parentWindow != null) parentWindow.pack(); } /** * {@inheritDoc} */ @Override protected void viewForModelRemoved( ConferenceCallPeerRenderer callPeerPanel, CallPeer callPeer) { /* * Remove the AWT Component of callPeerPanel from * the user interface hierarchy of this instance. */ mainPanel.remove(callPeerPanel.getComponent()); // If the parent window exists already try to adjust the size of the // window to the new content. Window parentWindow = SwingUtilities.getWindowAncestor(mainPanel); if (parentWindow != null) parentWindow.pack(); } /** * Implements a <tt>JViewport</tt> which allows us to install a custom * <tt>LayoutManager</tt>, namely {@link MyViewportLayout}. */ private static class MyViewport extends JViewport { /** * {@inheritDoc} * * Returns {@link MyViewportLayout#SHARED_INSTANCE} so that * <tt>MyViewport</tt> uses <tt>MyViewportLayout</tt>. */ @Override protected LayoutManager createLayoutManager() { return MyViewportLayout.SHARED_INSTANCE; } } /** * Implements a custom <tt>ViewportLayout</tt> which fixes the size of the * associated <tt>JViewport</tt> while resizing the <tt>Window</tt> which * contains the <tt>JScrollPane</tt>. */ private static class MyViewportLayout extends ViewportLayout { /** * The <tt>MyViewportLayout</tt> instance (to be) shared/used by all * <tt>MyViewport</tt> instances. */ static final MyViewportLayout SHARED_INSTANCE = new MyViewportLayout(); @Override public void layoutContainer(Container parent) { JViewport vp = (JViewport)parent; Component view = vp.getView(); Scrollable scrollableView = null; if (view == null) return; else if (view instanceof Scrollable) scrollableView = (Scrollable) view; /* All of the dimensions below are in view coordinates, except * vpSize which we're converting. */ Dimension viewPrefSize = view.getPreferredSize(); Dimension vpSize = vp.getSize(); Dimension extentSize = vp.toViewCoordinates(vpSize); Dimension viewSize = new Dimension(viewPrefSize); if (scrollableView != null) { if (scrollableView.getScrollableTracksViewportWidth()) viewSize.width = vpSize.width; if (scrollableView.getScrollableTracksViewportHeight()) viewSize.height = vpSize.height; } Point viewPosition = vp.getViewPosition(); /* If the new viewport size would leave empty space to the * right of the view, right justify the view or left justify * the view when the width of the view is smaller than the * container. */ if (scrollableView == null || vp.getParent() == null || vp.getParent().getComponentOrientation().isLeftToRight()) { if ((viewPosition.x + extentSize.width) > viewSize.width) { viewPosition.x = Math.max(0, viewSize.width - extentSize.width); } } else { if (extentSize.width > viewSize.width) { viewPosition.x = viewSize.width - extentSize.width; } else { viewPosition.x = Math.max(0, Math.min( viewSize.width - extentSize.width, viewPosition.x)); } } /* If the new viewport size would leave empty space below the * view, bottom justify the view or top justify the view when * the height of the view is smaller than the container. */ if ((viewPosition.y + extentSize.height) > viewSize.height) { viewPosition.y = Math.max(0, viewSize.height - extentSize.height); } /* If we haven't been advised about how the viewports size * should change wrt to the viewport, i.e. if the view isn't * an instance of Scrollable, then adjust the views size as follows. * * If the origin of the view is showing and the viewport is * bigger than the views preferred size, then make the view * the same size as the viewport. */ if (scrollableView == null) { if ((viewPosition.x == 0) && (vpSize.width > viewPrefSize.width)) { viewSize.width = vpSize.width; } if ((viewPosition.y == 0) && (vpSize.height > viewPrefSize.height)) { viewSize.height = vpSize.height; } } // Fixes incorrect size of the view. if (vpSize.width < viewSize.width) viewSize.width = vpSize.width; else if (vpSize.height < viewSize.height) viewSize.height = vpSize.height; vp.setViewPosition(viewPosition); vp.setViewSize(viewSize); } } }