/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package net.java.sip.communicator.util.swing; import java.awt.*; import java.util.*; import java.util.List; import javax.swing.*; /** * Implements the LayoutManager which lays out the local and remote * videos in a video Call. * * @author Lyubomir Marinov * @author Yana Stamcheva */ public class VideoLayout extends FitLayout { /** * The video canvas constraint. */ public static final String CANVAS = "CANVAS"; /** * The center remote video constraint. */ public static final String CENTER_REMOTE = "CENTER_REMOTE"; /** * The close local video constraint. */ public static final String CLOSE_LOCAL_BUTTON = "CLOSE_LOCAL_BUTTON"; /** * The east remote video constraint. */ public static final String EAST_REMOTE = "EAST_REMOTE"; /** * The local video constraint. */ public static final String LOCAL = "LOCAL"; /** * The ration between the local and the remote video. */ private static final float LOCAL_TO_REMOTE_RATIO = 0.30f; /** * The video canvas. */ private Component canvas; /** * The close local video button component. */ private Component closeButton; /** * The map of component constraints. */ private final HashMap constraints = new HashMap(); /** * The component containing the local video. */ private Component local; /** * The list of Components depicting remote videos. */ private final List remotes = new LinkedList(); /** * The x coordinate alignment of the remote video. */ private float remoteAlignmentX = Component.CENTER_ALIGNMENT; /** * Adds the given component in this layout on the specified by name * position. * * @param name the constraint giving the position of the component in this * layout * @param comp the component to add */ @Override public void addLayoutComponent(String name, Component comp) { super.addLayoutComponent(name, comp); synchronized (constraints) { this.constraints.put(comp, name); } if ((name == null) || name.equals(CENTER_REMOTE)) { remotes.add(comp); remoteAlignmentX = Component.CENTER_ALIGNMENT; } else if (name.equals(EAST_REMOTE)) { remotes.add(comp); remoteAlignmentX = Component.RIGHT_ALIGNMENT; } else if (name.equals(LOCAL)) local = comp; else if (name.equals(CLOSE_LOCAL_BUTTON)) closeButton = comp; else if (name.equals(CANVAS)) canvas = comp; } /** * Determines how may columns to use for the grid display of specific remote * visual/video Components. * * @param remotes the remote visual/video Components to be * displayed in a grid * @return the number of columns to use for the grid display of the * specified remote visual/video Components */ private int calculateColumnCount(List remotes) { return (remotes.size() > 4) ? 3 : 2; } /** * Returns the remote video component. * * @return the remote video component */ @Override protected Component getComponent(Container parent) { return (remotes.size() == 1) ? remotes.get(0) : null; } /** * Returns the constraints for the given component. * * @param c the component for which constraints we're looking for * @return the constraints for the given component */ public Object getComponentConstraints(Component c) { synchronized (constraints) { return constraints.get(c); } } /** * Returns the local video component. * * @return the local video component */ public Component getLocal() { return local; } /** * Returns the local video close button. * * @return the local video close button */ public Component getLocalCloseButton() { return closeButton; } /** * Lays out this given container. * * @param parent the container to lay out */ @Override public void layoutContainer(Container parent) { List remotes; Component local = getLocal(); /* * When there are multiple remote visual/video Components, the local one * will be displayed as if it is a remote one i.e. in the same grid, not * on top of a remote one. */ if ((this.remotes.size() > 1) && (local != null)) { remotes = new ArrayList(); remotes.addAll(this.remotes); remotes.add(local); } else remotes = this.remotes; int remoteCount = remotes.size(); Dimension parentSize = parent.getSize(); if (remoteCount == 1) { super.layoutContainer(parent, (local == null) ? Component.CENTER_ALIGNMENT : remoteAlignmentX); } else if (remoteCount > 0) { int columns = calculateColumnCount(remotes); int rows = (remoteCount + columns - 1) / columns; Rectangle bounds = new Rectangle( 0, 0, parentSize.width / columns, parentSize.height / rows); int columnsMinus1 = columns - 1; int i = 0; for (Component remote : remotes) { /* * We want the remote videos ordered from right to left so that * the local video does not cover a remote video when possible. */ bounds.x = (columnsMinus1 - (i % columns)) * bounds.width; bounds.y = (i / columns) * bounds.height; super.layoutComponent( remote, bounds, Component.CENTER_ALIGNMENT, Component.CENTER_ALIGNMENT); i++; if (i >= remoteCount) break; } } if (local != null) { /* * If the local visual/video Component is not displayed as if it is * a remote one, it will be placed on top of a remote one. */ if (!remotes.contains(local)) { Component remote0 = remotes.isEmpty() ? null : remotes.get(0); int localX; int localY; int height = Math.round(parentSize.height * LOCAL_TO_REMOTE_RATIO); int width = Math.round(parentSize.width * LOCAL_TO_REMOTE_RATIO); /* * XXX The remote Component being a JLabel is meant to signal * that there is no remote video and the remote is the * photoLabel. */ if ((remotes.size() == 1) && (remote0 instanceof JLabel)) { localX = parentSize.width/2 - width/2; localY = parentSize.height - height; super.layoutComponent( local, new Rectangle(localX, localY, width, height), Component.CENTER_ALIGNMENT, Component.BOTTOM_ALIGNMENT); } else { localX = ((remote0 == null) ? 0 : remote0.getX()) + 5; localY = parentSize.height - height - 5; super.layoutComponent( local, new Rectangle(localX, localY, width, height), Component.LEFT_ALIGNMENT, Component.BOTTOM_ALIGNMENT); } } if (closeButton != null) { super.layoutComponent( closeButton, new Rectangle( local.getX() + local.getWidth() - closeButton.getWidth(), local.getY(), closeButton.getWidth(), closeButton.getHeight()), Component.CENTER_ALIGNMENT, Component.CENTER_ALIGNMENT); } } if (canvas != null) { /* * The video canvas will get the locations of the other components * to paint so it has to cover the parent completely. */ canvas.setBounds(0, 0, parentSize.width, parentSize.height); } } /** * Returns the minimum layout size for the given container. * * @param parent the container which minimum layout size we're looking for * @return a Dimension containing, the minimum layout size for the given * container */ @Override public Dimension minimumLayoutSize(Container parent) { // TODO Auto-generated method stub return super.minimumLayoutSize(parent); } /** * Returns the preferred layout size for the given container. * * @param parent the container which preferred layout size we're looking for * @return a Dimension containing, the preferred layout size for the given * container */ @Override public Dimension preferredLayoutSize(Container parent) { List remotes; Component local = getLocal(); /* * When there are multiple remote visual/video Components, the local one * will be displayed as if it is a remote one i.e. in the same grid, not * on top of a remote one. */ if ((this.remotes.size() > 1) && (local != null)) { remotes = new ArrayList(); remotes.addAll(this.remotes); remotes.add(local); } else remotes = this.remotes; int remoteCount = remotes.size(); if (remoteCount == 0) { /* * If there is no remote visual/video Component, the local one will * serve the preferredSize of the Container. */ if (local != null) { Dimension preferredSize = local.getPreferredSize(); if (preferredSize != null) return preferredSize; } } else if (remoteCount == 1) { /* * If there is a single remote visual/video Component, the local one * will be on top of it so the remote one will serve the * preferredSize of the Container. */ Dimension preferredSize = remotes.get(0).getPreferredSize(); if (preferredSize != null) return preferredSize; } else if (remoteCount > 1) { int maxWidth = 0; int maxHeight = 0; for (Component remote : remotes) { Dimension preferredSize = remote.getPreferredSize(); if (preferredSize != null) { if (maxWidth < preferredSize.width) maxWidth = preferredSize.width; if (maxHeight < preferredSize.height) maxHeight = preferredSize.height; } } if ((maxWidth > 0) && (maxHeight > 0)) { int columns = calculateColumnCount(remotes); int rows = (remoteCount + columns - 1) / columns; return new Dimension(maxWidth * columns, maxHeight * rows); } } return super.preferredLayoutSize(parent); } /** * Removes the given component from this layout. * * @param comp the component to remove from the layout */ @Override public void removeLayoutComponent(Component comp) { super.removeLayoutComponent(comp); if (local == comp) local = null; else if (closeButton == comp) closeButton = null; else if (canvas == comp) canvas = null; else remotes.remove(comp); } }