/* * 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.io.*; import java.util.List; import javax.swing.*; import javax.swing.event.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.desktoputil.TransparentPanel; import org.jitsi.service.neomedia.*; import org.jitsi.service.neomedia.device.*; import org.jitsi.service.neomedia.format.*; import org.jitsi.util.OSUtils; import org.jitsi.util.swing.*; /** * A dialog dedicated to desktop streaming/sharing. Shows the possible screens * to select from to use for the streaming/sharing session. * * @author Yana Stamcheva */ public class SelectScreenDialog extends SIPCommDialog { /** * Serial version UID. */ private static final long serialVersionUID = 0L; /** * The object used for logging. */ private final static Logger logger = Logger.getLogger(SelectScreenDialog.class); /** * The combo box containing screen choice. */ private final DeviceComboBoxField deviceComboBox; /** * Wrapper for the device list field. */ private static class DeviceComboBoxField { /** * The combo box with the devices. */ private JComboBox deviceComboBox = null; /** * The <tt>JList</tt> with the devices. */ private JList deviceList = null; /** * The current component that displays the list with the devices. */ private Component deviceComponent; /** * A selection change listener. */ private Listener listener; /** * Constructs <tt>DeviceComboBoxField</tt> instance. * @param desktopDevices list with the available devices. * @param devicePanel the container of the field. */ public DeviceComboBoxField(Container devicePanel, List<MediaDevice> desktopDevices) { if(!OSUtils.IS_WINDOWS) { deviceComboBox = new JComboBox(desktopDevices.toArray()); deviceComboBox.setRenderer(new ComboRenderer()); devicePanel.add(deviceComboBox); deviceComponent = deviceComboBox; } else { deviceList = new JList(desktopDevices.toArray()); deviceList.setCellRenderer(new ComboRenderer()); JScrollPane listScroller = new JScrollPane(deviceList); listScroller.setPreferredSize(new Dimension(200, 38)); deviceList.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); deviceList.setLayoutOrientation(JList.VERTICAL); deviceList.setVisibleRowCount(-1); deviceList.setSelectedValue(desktopDevices.get(0), true); devicePanel.add(listScroller, BorderLayout.NORTH); deviceComponent = deviceList; } } /** * Returns the field component * @return the field component */ public Component getComponent() { return deviceComponent; } /** * Returns the selected device * @return the selected device */ public Object getSelectedItem() { return (deviceComboBox != null)? deviceComboBox.getSelectedItem() : deviceList.getSelectedValue(); } /** * Adds a listener to the field. * @param listener the listener to be added. */ public void addListener(final Listener listener) { this.listener = listener; if(deviceComboBox != null) { deviceComboBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { listener.onAction(); } }); } else { deviceList.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { listener.onAction(); } }); } } /** * Interface for the listener attached to the field. */ public static interface Listener { public void onAction(); } } /** * The cancel button of this dialog. */ private final JButton cancelButton = new JButton( GuiActivator.getResources().getI18NString("service.gui.CANCEL")); /** * The video <code>CaptureDeviceInfo</code> this instance started to create * the preview of. * <p> * Because the creation of the preview is asynchronous, it's possible to * request the preview of one and the same device multiple times. Which may * lead to failures because of, for example, busy devices and/or resources * (as is the case with LTI-CIVIL and video4linux2). * </p> */ private static MediaDevice videoDeviceInPreview; /** * The selected media device. */ private MediaDevice selectedDevice; /** * Creates an instance of <tt>SelectScreenDialog</tt> by specifying the list * of possible desktop devices to choose from. * * @param desktopDevices the list of possible desktop devices to choose * from */ public SelectScreenDialog(List<MediaDevice> desktopDevices) { setModal(true); setPreferredSize(new Dimension(400, 300)); Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); deviceComboBox = new DeviceComboBoxField(contentPane, desktopDevices); contentPane.add(createPreview(deviceComboBox)); contentPane.add(createButtonsPanel(), BorderLayout.SOUTH); } /** * Returns the selected device. * * @return the selected device */ public MediaDevice getSelectedDevice() { return selectedDevice; } /** * Creates the buttons panel. * * @return the buttons panel */ private Component createButtonsPanel() { JPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT)); JButton okButton = new JButton( GuiActivator.getResources().getI18NString("service.gui.OK")); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { selectedDevice = (MediaDevice) deviceComboBox.getSelectedItem(); dispose(); } }); buttonsPanel.add(okButton); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { selectedDevice = null; dispose(); } }); buttonsPanel.add(cancelButton); return buttonsPanel; } /** * Create preview component. * * @param comboBox the options. * @return the component. */ private static Component createPreview(final DeviceComboBoxField comboBox) { final JComponent preview; JLabel noPreview = new JLabel(GuiActivator.getResources().getI18NString( "impl.media.configform.NO_PREVIEW")); noPreview.setHorizontalAlignment(SwingConstants.CENTER); noPreview.setVerticalAlignment(SwingConstants.CENTER); preview = createVideoContainer(noPreview); preview.setPreferredSize(new Dimension(WIDTH, 280)); preview.setMaximumSize(new Dimension(WIDTH, 280)); final DeviceComboBoxField.Listener comboBoxListener = new DeviceComboBoxField.Listener() { public void onAction() { MediaDevice device = (MediaDevice) comboBox.getSelectedItem(); if ((device != null) && device.equals(videoDeviceInPreview)) return; Exception exception; try { createPreview(device, preview); exception = null; } catch (IOException ex) { exception = ex; } catch (MediaException ex) { exception = ex; } if (exception != null) { logger.error( "Failed to create preview for device " + device, exception); device = null; } videoDeviceInPreview = device; } }; comboBox.addListener(comboBoxListener); /* * We have to initialize the controls to reflect the configuration * at the time of creating this instance. Additionally, because the * video preview will stop when it and its associated controls * become unnecessary, we have to restart it when the mentioned * controls become necessary again. We'll address the two goals * described by pretending there's a selection in the video combo * box when the combo box in question becomes displayable. */ comboBox.getComponent().addHierarchyListener(new HierarchyListener() { public void hierarchyChanged(HierarchyEvent event) { if (((event.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) && comboBox.getComponent().isDisplayable()) { // let current changes end their execution // and after that trigger action on combobox SwingUtilities.invokeLater(new Runnable(){ public void run() { comboBoxListener.onAction(); } }); } else { if(!comboBox.getComponent().isDisplayable()) videoDeviceInPreview = null; } } }); return preview; } /** * Creates preview for the device(video) in the video container. * * @param device the device * @param videoContainer the container * @throws IOException a problem accessing the device. * @throws MediaException a problem getting preview. */ private static void createPreview( MediaDevice device, final JComponent videoContainer) throws IOException, MediaException { videoContainer.removeAll(); videoContainer.revalidate(); videoContainer.repaint(); if (device == null) return; Component c = (Component)GuiActivator.getMediaService() .getVideoPreviewComponent( device, videoContainer.getSize().width, videoContainer.getSize().height); videoContainer.add(c); } /** * Creates the video container. * * @param noVideoComponent the container component. * @return the video container. */ private static JComponent createVideoContainer(Component noVideoComponent) { return new VideoContainer(noVideoComponent, false); } /** * Custom combo box renderer. */ private static class ComboRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus); MediaDevice mediaDevice = (MediaDevice) value; Dimension screenSize = null; if (mediaDevice != null) screenSize = ((VideoMediaFormat) mediaDevice.getFormat()).getSize(); this.setText(screenSize.width + "x" + screenSize.height); return this; } } /** * Automatically press the cancel button when this dialog has been escaped. * * @param escaped indicates if this dialog has been closed by pressing the * ESC key */ @Override protected void close(boolean escaped) { if (escaped) cancelButton.doClick(); } }