package org.limewire.ui.swing.player; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ContainerAdapter; import java.awt.event.ContainerEvent; import java.awt.event.InputEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.AbstractAction; import javax.swing.Icon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.Timer; import net.miginfocom.swing.MigLayout; import org.jdesktop.application.Resource; import org.jdesktop.swingx.JXButton; import org.jdesktop.swingx.JXCollapsiblePane; import org.limewire.player.api.PlayerState; import org.limewire.ui.swing.components.Disposable; import org.limewire.ui.swing.components.HeaderBar; import org.limewire.ui.swing.components.decorators.HeaderBarDecorator; import org.limewire.ui.swing.painter.BorderPainter.AccentType; import org.limewire.ui.swing.painter.ButtonBackgroundPainter.DrawMode; import org.limewire.ui.swing.painter.factories.ButtonPainterFactory; import org.limewire.ui.swing.settings.SwingUiSettings; import org.limewire.ui.swing.util.GuiUtils; import org.limewire.ui.swing.util.I18n; import org.limewire.ui.swing.util.SwingHacks; import org.limewire.util.OSUtils; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; /** * Panel that holds video and video controls. */ class VideoPanel implements Disposable { private final HeaderBar headerBar = new HeaderBar(); private final JXCollapsiblePane headerBarCollapsiblePane = new JXCollapsiblePane(); private ActionListener collapsingActionListener = new ActionListener() { public void actionPerformed(ActionEvent evt) { headerBarCollapsiblePane.setCollapsed(true); } }; private Timer collapsePlayerControlsTimer = new Timer(2000, collapsingActionListener); @Resource private Icon fullScreenSelected; @Resource private Icon fullScreenUnselected; @Resource private Icon close; private final PlayerMediatorImpl playerMediator; private final Container videoRenderer; /** * the panel containing video and controls. */ private final JPanel videoPanel = new JPanel(new BorderLayout()); private JXButton fullScreenButton; private final PlayerControlPanel controlPanel; private final MigLayout fitToScreenLayout = new MigLayout("align 50% 50%, novisualpadding, gap 0, ins 0"); /** * Panel that holds the video panel. This is necessary to control whether or * not the video fits to screen. */ private final JPanel fitToScreenContainer = new JPanel(fitToScreenLayout); @Inject public VideoPanel(@Assisted Container videoRenderer, PlayerControlPanelFactory controlPanelFactory, HeaderBarDecorator headerBarDecorator, PlayerMediatorImpl playerMediator, ButtonPainterFactory buttonPainterFactory) { this.videoRenderer = videoRenderer; this.playerMediator = playerMediator; GuiUtils.assignResources(this); controlPanel = controlPanelFactory.createVideoControlPanel(); setUpHeaderBar(controlPanel, headerBarDecorator, buttonPainterFactory); setupActionMaps(); videoPanel.setBackground(Color.BLACK); this.videoRenderer.setBackground(Color.BLACK); fitToScreenContainer.setBackground(Color.BLACK); setUpMouseListener(videoPanel); setUpMouseListener(fitToScreenContainer); setUpMouseListener(videoRenderer); videoRenderer.addContainerListener(new ContainerAdapter() { @Override public void componentAdded(ContainerEvent e) { setUpMouseListener(e.getChild()); //ensure everything is sized properly and video is embedded setFitToScreen(SwingUiSettings.VIDEO_FIT_TO_SCREEN.getValue()); } }); fitToScreenContainer.add(this.videoRenderer); setFitToScreen(SwingUiSettings.VIDEO_FIT_TO_SCREEN.getValue()); collapsePlayerControlsTimer.setRepeats(false); headerBarCollapsiblePane.setContentPane(headerBar); videoPanel.add(headerBarCollapsiblePane, BorderLayout.NORTH); videoPanel.add(fitToScreenContainer, BorderLayout.CENTER); } public JComponent getComponent(){ return videoPanel; } public void dispose() { collapsePlayerControlsTimer.removeActionListener(collapsingActionListener); collapsingActionListener = null; collapsePlayerControlsTimer = null; controlPanel.dispose(); } public void requestFocus() { headerBar.requestFocusInWindow(); } /** * Guarantees that everything displays correctly. Call this after the player * has been loaded and VideoPanel and any renderer components have been * added to the GUI and shown. */ public void playerLoaded() { headerBar.requestFocusInWindow(); if (playerMediator.isFullScreen()) { setFitToScreen(true); fullScreenButton.setIcon(fullScreenSelected); if (!OSUtils.isMacOSX()) collapsePlayerControlsTimer.restart(); } else { setFitToScreen(SwingUiSettings.VIDEO_FIT_TO_SCREEN.getValue()); fullScreenButton.setIcon(fullScreenUnselected); } } private void setupActionMaps(){ videoPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_MASK), "fullScreen"); videoPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_F11, 0), "fullScreen"); videoPanel.getActionMap().put("fullScreen", new ToggleFullScreen()); videoPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "esc"); videoPanel.getActionMap().put("esc", new EscAction()); videoPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "space"); videoPanel.getActionMap().put("space", new PlayOrPauseAction()); } private void setUpMouseListener(Component videoComponent) { VideoPanelMouseListener listener = new VideoPanelMouseListener(); videoComponent.addMouseListener(listener); videoComponent.addMouseMotionListener(listener); } private void setUpHeaderBar(JComponent controlPanel, HeaderBarDecorator headerBarDecorator, ButtonPainterFactory buttonPainterFactory) { //It's definitely on the strange side but making the header bar focusable makes the keyboard shortcuts work in full screen. headerBar.setFocusable(true); fullScreenButton = new JXButton(fullScreenUnselected); fullScreenButton.setContentAreaFilled(false); fullScreenButton.setFocusPainted(false); fullScreenButton.setBackgroundPainter(buttonPainterFactory.createDarkFullButtonBackgroundPainter(DrawMode.FULLY_ROUNDED, AccentType.SHADOW)); fullScreenButton.addActionListener(new FullScreenListener()); final JXButton closeButton = new JXButton(close); closeButton.setContentAreaFilled(false); closeButton.setFocusPainted(false); closeButton.setBackgroundPainter(buttonPainterFactory.createDarkFullButtonBackgroundPainter(DrawMode.FULLY_ROUNDED, AccentType.SHADOW)); closeButton.addActionListener(new CloseAction()); headerBarDecorator.decorateBasic(headerBar); headerBar.setLayout(new MigLayout()); headerBar.add(fullScreenButton, "right, push"); headerBar.add(controlPanel, "pos 0.5al 0.5al"); headerBar.add(closeButton); } private JMenuItem createFullScreenMenuItem(){ JMenuItem item = new JCheckBoxMenuItem(I18n.tr("Full Screen")); item.setSelected(playerMediator.isFullScreen()); item.addActionListener(new FullScreenListener()); return item; } private JMenuItem createPlayMenuItem() { JMenuItem item = new JMenuItem(I18n.tr("Play")); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { playerMediator.resume(); } }); return item; } private JMenuItem createFitToScreenMenuItem() { JMenuItem item = new JCheckBoxMenuItem(I18n.tr("Fit to Screen")); item.setSelected(SwingUiSettings.VIDEO_FIT_TO_SCREEN.getValue()); item.addItemListener(new FitToScreenListener()); return item; } private void setFitToScreen(boolean isFitToScreen) { if(isFitToScreen){ fitToScreenLayout.setComponentConstraints(videoRenderer, "grow, push"); videoRenderer.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE)); videoRenderer.setMinimumSize(new Dimension(0, 0)); } else { fitToScreenLayout.setComponentConstraints(videoRenderer, ""); videoRenderer.setMaximumSize(videoRenderer.getPreferredSize()); videoRenderer.setMinimumSize(new Dimension(0, 0)); } fitToScreenContainer.revalidate(); } private JMenuItem createPauseMenuItem(){ JMenuItem item = new JMenuItem(I18n.tr("Pause")); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { playerMediator.pause(); } }); return item; } private JMenuItem createCloseItem(){ JMenuItem item = new JMenuItem(I18n.tr("Close Video")); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { playerMediator.stop(); } }); return item; } private class CloseAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { playerMediator.stop(); } } private class FullScreenListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { playerMediator.setFullScreen(!playerMediator.isFullScreen()); } } private class ToggleFullScreen extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { playerMediator.setFullScreen(!playerMediator.isFullScreen()); } } private class FitToScreenListener implements ItemListener { @Override public void itemStateChanged(ItemEvent e) { setFitToScreen(e.getStateChange() == ItemEvent.SELECTED); //save the Fit_To_Screen state in settings. SwingUiSettings.VIDEO_FIT_TO_SCREEN.setValue(e.getStateChange() == ItemEvent.SELECTED); } } private class EscAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { if (playerMediator.isFullScreen()) { playerMediator.setFullScreen(false); } else { playerMediator.stop(); } } } private class PlayOrPauseAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { if (playerMediator.getStatus() == PlayerState.PLAYING) { playerMediator.pause(); } else { playerMediator.resume(); } } } private class VideoPanelMouseListener extends MouseAdapter { private int mouseX; private int mouseY; @Override public void mouseMoved(MouseEvent e) { /* * On Windows the resizing control panel is causing mouse moved events to occur even when the * mouse is not really moving. (It's only moving relative to the growing video panel.) * So, let's try to separate this sort of motion from real user input by checking whether * the mouse has actually moved with regard to its location on the screen. */ if (playerMediator.isFullScreen() && (e.getLocationOnScreen().x != mouseX || e.getLocationOnScreen().y != mouseY)) { headerBarCollapsiblePane.setCollapsed(false); if (!OSUtils.isMacOSX()) collapsePlayerControlsTimer.restart(); mouseX = e.getLocationOnScreen().x; mouseY = e.getLocationOnScreen().y; } } @Override public void mousePressed(MouseEvent e) { maybeShowPopup(e); } @Override public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger()) { JPopupMenu menu = new JPopupMenu(); SwingHacks.fixPopupMenuForWindows(menu); if (playerMediator.getStatus() == PlayerState.PLAYING) { menu.add(createPauseMenuItem()); } else { menu.add(createPlayMenuItem()); } menu.addSeparator(); if(!playerMediator.isFullScreen()) { menu.add(createFitToScreenMenuItem()); } menu.add(createFullScreenMenuItem()); menu.addSeparator(); menu.add(createCloseItem()); menu.show(e.getComponent(), e.getX(), e.getY()); } } } }