/* * 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 java.awt.geom.*; import java.beans.*; import java.util.*; import javax.swing.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.main.contactlist.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.desktoputil.transparent.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.Logger; import org.jitsi.service.neomedia.*; import org.jitsi.util.*; /** * * @author Yana Stamcheva */ public class DesktopSharingFrame { /** * Used for logging. */ private static final Logger logger = Logger.getLogger(DesktopSharingFrame.class); /** * The icon shown to indicate the resize drag area. */ private static final ImageIcon resizeIcon = GuiActivator.getResources().getImage( "service.gui.icons.WINDOW_RESIZE_ICON"); /** * The indent of the sharing region from the frame. */ private static int SHARING_REGION_INDENT = 2; /** * The x coordinate of the frame, which started the regional sharing. */ private static int initialFrameX = -1; /** * The y coordinate of the frame, which started the regional sharing. */ private static int initialFrameY = -1; /** * The width of the sharing region, which started the sharing. */ private static int sharingRegionWidth = -1; /** * The height of the sharing region, which started the sharing. */ private static int sharingRegionHeight = -1; /** * A mapping of a desktop sharing frame created by this class and a call. */ private static final Map<Call, JFrame> callDesktopFrames = new Hashtable<Call, JFrame>(); /** * Creates the transparent desktop sharing frame. * * @param protocolProvider the protocol provider, through which the desktop * sharing will pass * @param contactAddress the address of the contact to call * @param uiContact the <tt>UIContactImpl</tt> for which we create a * desktop sharing frame * @param initialFrame indicates if this is the frame which initiates the * desktop sharing * @return the created desktop sharing frame */ public static TransparentFrame createTransparentFrame( ProtocolProviderService protocolProvider, String contactAddress, UIContactImpl uiContact, boolean initialFrame) { TransparentFrame frame = TransparentFrame.createTransparentFrame(); initContentPane(frame, initialFrame); JComponent sharingRegion = createSharingRegion(initialFrame); frame.getContentPane().add(sharingRegion, BorderLayout.NORTH); JPanel buttonPanel = initButtons( frame, sharingRegion, initialFrame, null, protocolProvider, contactAddress, uiContact); frame.getContentPane().add(buttonPanel, BorderLayout.SOUTH); frame.pack(); return frame; } /** * Creates the transparent desktop sharing frame. * * @param call the current call * @param initialFrame indicates if this is the frame which initiates the * desktop sharing * @return the created desktop sharing frame */ public static TransparentFrame createTransparentFrame( Call call, boolean initialFrame) { TransparentFrame frame = TransparentFrame.createTransparentFrame(); initContentPane(frame, initialFrame); JComponent sharingRegion = createSharingRegion(initialFrame); frame.getContentPane().add(sharingRegion, BorderLayout.NORTH); JPanel buttonPanel = initButtons( frame, sharingRegion, initialFrame, call, null, null, null); frame.getContentPane().add(buttonPanel, BorderLayout.SOUTH); // If the desktop sharing has started we store the frame to call mapping. if (!initialFrame) { callDesktopFrames.put(call, frame); addCallListener(call, frame); addFrameListener(call, frame, sharingRegion); addDesktopSharingListener(call, frame); logger.info("The sharing region width: " + sharingRegionWidth); if (sharingRegionWidth > -1 && sharingRegionHeight > -1) sharingRegion.setPreferredSize( new Dimension(sharingRegionWidth, sharingRegionHeight)); frame.pack(); if (initialFrameX != -1 || initialFrameY != -1) frame.setLocation(initialFrameX, initialFrameY); else // By default we position the frame in the center of the screen. // It's important to call this method after the pack(), because // it requires the frame size to calculate the location. frame.setLocationRelativeTo(null); } else { frame.pack(); // By default we position the frame in the center of the screen. // It's important to call this method after the pack(), because // it requires the frame size to calculate the location. frame.setLocationRelativeTo(null); } return frame; } /** * Get the frame for a <tt>Call</tt>. * * @param call the <tt>Call</tt> * @return JFrame for the call or null if not found */ public static JFrame getFrameForCall(Call call) { return callDesktopFrames.get(call); } /** * Adds a call listener, which listens for call ended events and would * close any related desktop sharing frames when a call is ended. * * @param call the call, for which we're registering a listener * @param frame the frame to be closed on call ended */ private static void addCallListener(Call call, JFrame frame) { OperationSetBasicTelephony<?> basicTelephony = call.getProtocolProvider().getOperationSet( OperationSetBasicTelephony.class); if (basicTelephony != null) // This should always be true. { basicTelephony.addCallListener( new CallListener() { /** * Implements {@link CallListener#callEnded(CallEvent)}. * Disposes of the frame related to the ended call. * * @param ev a <tt>CallEvent</tt> which identifies the * ended call */ public void callEnded(CallEvent ev) { Call call = ev.getSourceCall(); JFrame desktopFrame = callDesktopFrames.get(call); if (desktopFrame != null) { desktopFrame.dispose(); callDesktopFrames.remove(call); } } public void incomingCallReceived(CallEvent ev) {} public void outgoingCallCreated(CallEvent ev) {} }); } } /** * Adds the desktop sharing listener. * * @param call the call, for which we're registering a listener * @param frame the frame to be closed on call ended */ private static void addDesktopSharingListener(final Call call, JFrame frame) { OperationSetVideoTelephony videoTelephony = call.getProtocolProvider().getOperationSet( OperationSetVideoTelephony.class); videoTelephony.addPropertyChangeListener( call, new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { if (OperationSetVideoTelephony.LOCAL_VIDEO_STREAMING .equals(ev.getPropertyName()) && MediaDirection.RECVONLY.equals( ev.getNewValue())) { JFrame desktopFrame = callDesktopFrames.get(call); if (desktopFrame != null) { desktopFrame.dispose(); callDesktopFrames.remove(call); } } } }); } /** * Initializes the content pane of the given window. * * @param frame the parent frame * @param initialFrame indicates if this is the frame which initiates the * desktop sharing */ private static void initContentPane(JFrame frame, boolean initialFrame) { JPanel contentPane = new JPanel() { /** * Serial version UID. */ public static final long serialVersionUID = 0L; @Override protected void paintComponent(Graphics g) { super.paintComponent(g); g = g.create(); try { AntialiasingManager.activateAntialiasing(g); Graphics2D g2d = (Graphics2D)g; g2d.setStroke(new BasicStroke(4)); if (TransparentFrame.isTranslucencySupported) { g2d.setColor(new Color( Color.DARK_GRAY.getRed(), Color.DARK_GRAY.getGreen(), Color.DARK_GRAY.getBlue(), 180)); g2d.drawRoundRect( 0, 0, getWidth() - 1, getHeight() - 1, 20, 20); } else { g.setColor(Color.DARK_GRAY); g.drawRect(0, 0, getWidth() - 1, getHeight() - 1); } } finally { g.dispose(); } } }; contentPane.setOpaque(false); contentPane.setDoubleBuffered(false); contentPane.setLayout(new BorderLayout()); contentPane.setBorder(BorderFactory.createEmptyBorder( SHARING_REGION_INDENT, SHARING_REGION_INDENT, SHARING_REGION_INDENT, SHARING_REGION_INDENT)); frame.setContentPane(contentPane); ComponentMover.registerComponent(frame); // On Linux transparency is supported but mouse events do not pass // through. if (TransparentFrame.isTranslucencySupported && !OSUtils.IS_LINUX) frame.setAlwaysOnTop(true); } /** * Creates the sharing region. * * @param initialFrame indicates if this is the frame which initiates the * desktop sharing * @return the created sharing region */ private static JComponent createSharingRegion(boolean initialFrame) { JComponent sharingRegion = new TransparentPanel(new BorderLayout()); // The preferred width on MacOSX should be a multiple of 16, that's why // we put 592 as a default width. On the other hand the width on // Windows or Linux should be preferably a multiple of 2. if (OSUtils.IS_MAC) sharingRegion.setPreferredSize(new Dimension(592, 400)); else sharingRegion.setPreferredSize(new Dimension(600, 400)); sharingRegion.setDoubleBuffered(false); if (!TransparentFrame.isTranslucencySupported && !initialFrame) { JLabel label = new JLabel(GuiActivator.getResources() .getI18NString("service.gui.DRAG_FOR_SHARING"), JLabel.CENTER); label.setForeground(Color.GRAY); sharingRegion.add(label); } return sharingRegion; } /** * Creates and initializes the button panel. * * @param frame the parent frame * @param sharingRegion the sharing region component * @param initialFrame indicates if this is the frame which initiates the * desktop sharing * @param call the current call, if we're in a call * @param protocolProvider the protocol provider * @param contact the contact, which is the receiver of the call * @param uiContact the <tt>UIContactImpl</tt> for which we create the * desktop sharing frame * * @return the created button panel */ private static JPanel initButtons( final JFrame frame, final JComponent sharingRegion, boolean initialFrame, final Call call, final ProtocolProviderService protocolProvider, final String contact, final UIContactImpl uiContact) { JPanel buttonPanel = new JPanel(new GridBagLayout()) { /** * Serial version UID. */ public static final long serialVersionUID = 0L; @Override public void paintComponent(Graphics g) { // We experience some problems making this component // semi-transparent on Linux. if (!TransparentFrame.isTranslucencySupported || OSUtils.IS_LINUX) { super.paintComponent(g); return; } // On all other operating systems supporting transparency we'll // make this component semi-transparent. Graphics2D g2d = (Graphics2D) g.create(); AntialiasingManager.activateAntialiasing(g2d); g2d.setColor(new Color( Color.DARK_GRAY.getRed(), Color.DARK_GRAY.getGreen(), Color.DARK_GRAY.getBlue(), 180)); GeneralPath shape = new GeneralPath(); int x = -SHARING_REGION_INDENT + 2; int y = 0; int width = getWidth() + SHARING_REGION_INDENT*2 - 4; int height = getHeight() + SHARING_REGION_INDENT*2 - 2; shape.moveTo(x, y); shape.lineTo(width, y); shape.lineTo(width, height - 12); shape.curveTo(width, height - 12, width, height, width - 12, height); shape.lineTo(12, height); shape.curveTo(12, height, x, height, x, height - 12); shape.lineTo(x, y); shape.closePath(); g2d.fill(shape); g2d.setColor(getBackground()); } }; GridBagConstraints constraints = new GridBagConstraints(); // We experience some problems making this component semi-transparent on // Linux. if (TransparentFrame.isTranslucencySupported && !OSUtils.IS_LINUX) { buttonPanel.setOpaque(false); } else { buttonPanel.setBackground(Color.DARK_GRAY); } buttonPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); buttonPanel.setPreferredSize( new Dimension(sharingRegion.getWidth(), 30)); if (initialFrame) { JButton startButton = createButton( GuiActivator.getResources() .getI18NString("service.gui.START_SHARING")); JButton cancelButton = createButton( GuiActivator.getResources() .getI18NString("service.gui.CANCEL")); constraints.gridx = 0; constraints.gridy = 0; constraints.weightx = 1.0; constraints.insets = new Insets(0, 0, 0, 5); constraints.anchor = GridBagConstraints.EAST; buttonPanel.add(cancelButton, constraints); constraints.gridx = 1; constraints.gridy = 0; constraints.weightx = 1.0; constraints.anchor = GridBagConstraints.WEST; buttonPanel.add(startButton, constraints); constraints.gridx = 3; constraints.gridy = 0; constraints.weightx = 0; constraints.insets = new Insets(0, 0, 0, 0); constraints.anchor = GridBagConstraints.SOUTHWEST; buttonPanel.add( createResizeLabel(frame, sharingRegion, buttonPanel), constraints); startButton.setCursor(Cursor.getDefaultCursor()); cancelButton.setCursor(Cursor.getDefaultCursor()); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { frame.dispose(); } }); startButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Point location = sharingRegion.getLocationOnScreen(); sharingRegionWidth = sharingRegion.getWidth(); sharingRegionHeight = sharingRegion.getHeight(); initialFrameX = frame.getX(); initialFrameY = frame.getY(); frame.dispose(); if (call != null) CallManager.enableRegionDesktopSharing( call, location.x, location.y, sharingRegionWidth, sharingRegionHeight); else CallManager.createRegionDesktopSharing( protocolProvider, contact, uiContact, location.x, location.y, sharingRegionWidth, sharingRegionHeight); } }); } else { JButton stopButton = createButton( GuiActivator.getResources() .getI18NString("service.gui.STOP_SHARING")); buttonPanel.add(stopButton); stopButton.setCursor(Cursor.getDefaultCursor()); stopButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (call != null) CallManager.enableDesktopSharing(call, false); frame.dispose(); } }); } return buttonPanel; } /** * Creates a button with the given text. * * @param text the text of the button * @return the created button */ private static JButton createButton(String text) { JButton button = new JButton(text); button.setOpaque(false); return button; } /** * Creates the label allowing to resize the given frame. * * @param frame the frame to resize * @param sharingRegion the sharing region * @param buttonPanel the button panel, where the created label would be * added * @return the created resize label */ private static JLabel createResizeLabel(final JFrame frame, final JComponent sharingRegion, final JComponent buttonPanel) { final JLabel resizeLabel = new JLabel(resizeIcon); resizeLabel.setCursor( Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR)); resizeLabel.addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseDragged(MouseEvent e) { Point p = e.getPoint(); SwingUtilities.convertPointToScreen(p, resizeLabel); Point regionLocation = sharingRegion.getLocationOnScreen(); int sharingWidth = (int) ( p.getX() - regionLocation.getX() - 2*SHARING_REGION_INDENT); int newSharingHeight = (int) (p.getY() - frame.getY() - buttonPanel.getHeight() - 2*SHARING_REGION_INDENT); // We should make sure that the width on MacOSX is a multiple // of 16. if (OSUtils.IS_MAC && sharingWidth%16 > 0) { sharingWidth = Math.round(sharingWidth/16f)*16; } else if (sharingWidth%2 > 0) { sharingWidth = Math.round(sharingWidth/2f)*2; } sharingRegion.setPreferredSize( new Dimension(sharingWidth, newSharingHeight)); frame.validate(); int height = (int) (p.getY() - frame.getY()); frame.setSize(sharingWidth + 2*SHARING_REGION_INDENT, height); } }); return resizeLabel; } /** * Adds a listener for the given frame and call * * @param call the underlying call * @param frame the frame to which the listener would be added * @param sharingRegion the sharing region */ private static void addFrameListener( final Call call, final JFrame frame, final Component sharingRegion) { frame.addComponentListener(new ComponentListener() { public void componentResized(ComponentEvent e) {} public void componentMoved(ComponentEvent e) { OperationSetDesktopStreaming desktopOpSet = call.getProtocolProvider().getOperationSet( OperationSetDesktopStreaming.class); if (desktopOpSet == null) return; Point location = new Point( sharingRegion.getX(), sharingRegion.getY()); SwingUtilities.convertPointToScreen(location, frame.getContentPane()); desktopOpSet.movePartialDesktopStreaming( call, location.x, location.y); } public void componentShown(ComponentEvent e) {} public void componentHidden(ComponentEvent arg0) {} }); } }