/*
 * @(#)AbstractVideoView.java  1.4  2005-01-04
 *
 * Copyright (c) 2000 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package ch.randelshofer.view;

import ch.randelshofer.gui.ScaleFactorLayout;
import ch.randelshofer.gui.ScaleFactorConstraints;
import org.monte.media.gui.ImagePanel;
import ch.randelshofer.util.ResourceBundleUtil;
import ch.randelshofer.util.PropertyModel;

import java.awt.*;
import java.awt.dnd.*;
import java.awt.event.*;
import java.awt.image.*;

import java.beans.PropertyChangeEvent;

import javax.swing.*;
import javax.swing.event.*;

/**
 * This class provides the layout and common menu items for video media content.
 *
 * @author Werner Randelshofer, Hausmatt 10, CH-6405 Immensee, Switzerland
 * @version 1.4 2005-01-04 Fullscreen view takes now screen insets into account.
 * <br>1.3 2003-04-07 Automatic start of the player when the 'autoPlay'
 * feature is enabled delegated to subclasses.
 * <br>1.2 2003-04-19 The form editor in NetBeans 3.4 does not support
 * abstract classes. This class is now concrete.
 * <br>1.1 2002-02-10 Support for scroll bars added. DnD support added.
 * <br>1.0 2000-10-02
 */
public class AbstractVideoView extends AbstractView implements ActionListener, ItemListener {

    protected VideoPanel videoPanel;
    protected JScrollPane scrollPane;
    /**
     * Resource bundle for internationalisation.
     */
    private ResourceBundleUtil labels;
    /** Menu items. */
    protected JRadioButtonMenuItem ignorePixelAspectItem,  multipliedPixelAspectItem,  exactPixelAspectItem,  anamorphPixelAspectItem;
    protected JRadioButtonMenuItem ignoreImageAspectItem,  exactImageAspectItem;
    protected JRadioButtonMenuItem backgroundWhiteItem,  backgroundBlackItem,  backgroundCheckerboardItem;
    protected JRadioButtonMenuItem nearestNeighborItem,  bilinearItem,  bicubicItem;
    protected JCheckBoxMenuItem autoPlayItem;
    protected JCheckBoxMenuItem showScrollbarsItem;
    protected JCheckBoxMenuItem playEveryFrameItem;
    protected JCheckBoxMenuItem loopItem;
    protected JCheckBoxMenuItem audioEnabledItem;
    protected final static BufferedImage checkerboardTexture;
    

    static {
        checkerboardTexture = new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = checkerboardTexture.createGraphics();
        g.setColor(Color.white);
        g.fillRect(0, 0, 16, 16);
        g.setColor(new Color(223, 223, 223));
        g.fillRect(0, 0, 8, 8);
        g.fillRect(8, 8, 8, 8);
        g.dispose();
    }
    protected final static RenderingHints RENDER_NEAREST_NEIGHBOR;
    

    static {
        RenderingHints rh = new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
        rh.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        rh.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED);
        rh.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
        rh.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
        rh.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
        rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        rh.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        rh.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
        RENDER_NEAREST_NEIGHBOR = rh;
    }
    protected final static RenderingHints RENDER_BILINEAR;
    

    static {
        RenderingHints rh = new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        rh.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        rh.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED);
        rh.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
        rh.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
        rh.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        rh.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        rh.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
        RENDER_BILINEAR = rh;
    }
    protected final static RenderingHints RENDER_BICUBIC;
    

    static {
        RenderingHints rh = new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        rh.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        rh.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED);
        rh.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
        rh.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
        rh.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        rh.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        rh.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
        RENDER_BICUBIC = rh;
    }
    /** Layout constraints for the video component. */
    private ScaleFactorConstraints videoLayoutConstraints;
    public final static int HALF_SIZE = 0;
    public final static int NORMAL_SIZE = 1;
    public final static int DOUBLE_SIZE = 2;
    public final static int SCREEN_SIZE = 3;

    /** Initializes the Form */
    public AbstractVideoView() {
        labels = ResourceBundleUtil.getLAFBundle("ch.randelshofer.view.AbstractVideoView");
        initComponents();
        initMoreComponents();

        // Experimental
        setDoubleBuffered(false);
    }

    private void initMoreComponents() {
        scrollPane = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        videoPanel = new VideoPanel();
        videoPanel.setLayout(
                new ScaleFactorLayout() {

                    public void layoutContainer(Container target) {
                        super.layoutContainer(target);

                        updateScaleFactor();
                    }
                });
        videoPanel.setBackground(Color.black);
        add(videoPanel, BorderLayout.CENTER);

        // Constraints are used for image aspect and scale factor properties
        videoLayoutConstraints = new ScaleFactorConstraints(1, 1, true);

        // Readjust controller panel on Macintosh (avoids that size intrude box
        // on Macintosh windows hides the controller partially).
        String lafID = UIManager.getLookAndFeel().getID();
        strutPanel.setVisible(lafID.equals("Platinum") || lafID.indexOf("Mac") != -1 || lafID.equals("Aqua"));

        // Menu Items for image aspect
        ButtonGroup group = new ButtonGroup();
        group.add(ignoreImageAspectItem = new JRadioButtonMenuItem(labels.getString("ignoreImageAspect")));
        ignoreImageAspectItem.setMnemonic(labels.getMnemonic("ignoreImageAspectMnem"));
        ignoreImageAspectItem.setAccelerator(labels.getKeyStroke("ignoreImageAspectAcc"));
        ignoreImageAspectItem.addItemListener(this);
        group.add(exactImageAspectItem = new JRadioButtonMenuItem(labels.getString("exactImageAspect")));
        exactImageAspectItem.addItemListener(this);
        exactImageAspectItem.setMnemonic(labels.getMnemonic("exactImageAspectMnem"));
        exactImageAspectItem.setAccelerator(labels.getKeyStroke("exactImageAspectAcc"));

        // Menu items for pixel aspect
        group = new ButtonGroup();
        ignorePixelAspectItem = new JRadioButtonMenuItem(labels.getString("ignorePixelAspect"));
        ignorePixelAspectItem.setMnemonic(labels.getMnemonic("ignorePixelAspectMnem"));
        ignorePixelAspectItem.setAccelerator(labels.getKeyStroke("ignorePixelAspectAcc"));
        group.add(ignorePixelAspectItem);
        ignorePixelAspectItem.addItemListener(this);
        group.add(multipliedPixelAspectItem = new JRadioButtonMenuItem(labels.getString("roundedPixelAspect")));
        multipliedPixelAspectItem.setMnemonic(labels.getMnemonic("roundedPixelAspectMnem"));
        multipliedPixelAspectItem.setAccelerator(labels.getKeyStroke("roundedPixelAspectAcc"));
        multipliedPixelAspectItem.addItemListener(this);
        group.add(exactPixelAspectItem = new JRadioButtonMenuItem(labels.getString("exactPixelAspect")));
        exactPixelAspectItem.addItemListener(this);
        exactPixelAspectItem.setMnemonic(labels.getMnemonic("exactPixelAspectMnem"));
        exactPixelAspectItem.setAccelerator(labels.getKeyStroke("exactPixelAspectAcc"));
        group.add(anamorphPixelAspectItem = new JRadioButtonMenuItem(labels.getString("anamorphPixelAspect")));
        anamorphPixelAspectItem.addItemListener(this);
        anamorphPixelAspectItem.setMnemonic(labels.getMnemonic("anamorphPixelAspectMnem"));
        anamorphPixelAspectItem.setAccelerator(labels.getKeyStroke("anamorphPixelAspectAcc"));

        // Menu Items for background color
        group = new ButtonGroup();
        group.add(backgroundBlackItem = new JRadioButtonMenuItem(labels.getString("blackBackground")));
        backgroundBlackItem.setMnemonic(labels.getMnemonic("blackBackgroundMnem"));
        backgroundBlackItem.setAccelerator(labels.getKeyStroke("blackBackgroundAcc"));
        group.add(backgroundWhiteItem = new JRadioButtonMenuItem(labels.getString("whiteBackground")));
        backgroundWhiteItem.setMnemonic(labels.getMnemonic("whiteBackgroundMnem"));
        backgroundWhiteItem.setAccelerator(labels.getKeyStroke("whiteBackgroundAcc"));
        group.add(backgroundCheckerboardItem = new JRadioButtonMenuItem(labels.getString("checkerboardBackground")));
        backgroundCheckerboardItem.setMnemonic(labels.getMnem("checkerboardBackground"));
        backgroundCheckerboardItem.setAccelerator(labels.getAcc("checkerboardBackground"));
        backgroundBlackItem.addItemListener(this);
        backgroundWhiteItem.addItemListener(this);
        backgroundCheckerboardItem.addItemListener(this);

        // Menu items for interpolation
        group = new ButtonGroup();
        group.add(nearestNeighborItem = new JRadioButtonMenuItem(labels.getString("nearestNeighbor")));
        nearestNeighborItem.setMnemonic(labels.getMnem("nearestNeighbor"));
        nearestNeighborItem.setAccelerator(labels.getAcc("nearestNeighbor"));
        group.add(bilinearItem = new JRadioButtonMenuItem(labels.getString("bilinear")));
        bilinearItem.setMnemonic(labels.getMnem("bilinear"));
        bilinearItem.setAccelerator(labels.getAcc("bilinear"));
        group.add(bicubicItem = new JRadioButtonMenuItem(labels.getString("bicubic")));
        bicubicItem.setMnemonic(labels.getMnem("bicubic"));
        bicubicItem.setAccelerator(labels.getAcc("bicubic"));
        nearestNeighborItem.addItemListener(this);
        bilinearItem.addItemListener(this);
        bicubicItem.addItemListener(this);

        // Menu items for auto-play
        autoPlayItem = new JCheckBoxMenuItem(labels.getString("autoPlay"));
        autoPlayItem.setMnemonic(labels.getMnemonic("autoPlayMnem"));
        autoPlayItem.setAccelerator(labels.getKeyStroke("autoPlayAcc"));
        // autoPlayItem.addActionListener(this);
        autoPlayItem.addItemListener(this);

        // Menu items for scrollbars
        showScrollbarsItem = new JCheckBoxMenuItem(labels.getString("showScrollbars"));
        showScrollbarsItem.setMnemonic(labels.getMnemonic("showScrollbarsMnem"));
        showScrollbarsItem.setAccelerator(labels.getKeyStroke("showScrollbarsAcc"));
        showScrollbarsItem.addItemListener(this);

        playEveryFrameItem = new JCheckBoxMenuItem(labels.getString("playEveryFrame"));
        playEveryFrameItem.setMnemonic(labels.getMnemonic("playEveryFrameMnem"));
        playEveryFrameItem.setAccelerator(labels.getKeyStroke("playEveryFrameAcc"));
        playEveryFrameItem.addItemListener(this);
        loopItem = new JCheckBoxMenuItem(labels.getString("loop"));
        loopItem.setMnemonic(labels.getMnemonic("loopMnem"));
        loopItem.setAccelerator(labels.getKeyStroke("loopAcc"));
        loopItem.addItemListener(this);
        audioEnabledItem = new JCheckBoxMenuItem(labels.getString("audioEnabled"));
        audioEnabledItem.setMnemonic(labels.getMnemonic("audioEnabledMnem"));
        audioEnabledItem.setAccelerator(labels.getKeyStroke("audioEnabledAcc"));
        audioEnabledItem.addItemListener(this);
    }

    protected void setVideoComponent(Component c) {
        videoPanel.removeAll();
        videoPanel.setVisible(c != null);
        if (c != null) {
            videoPanel.add(c, videoLayoutConstraints);
        }
    }

    protected Component getVideoComponent() {
        return (videoPanel.getComponentCount() == 0) ? null : videoPanel.getComponent(0);
    }

    protected void setControllerComponent(Component c) {
        controllerPanel.removeAll();
        if (c != null) {
            controllerPanel.add(c, BorderLayout.CENTER);
        }
        controllerPanel.validate();
    }

    protected Component getControllerComponent() {
        return (controllerPanel.getComponentCount() == 0) ? null : controllerPanel.getComponent(0);
    }

    public void mergeViewMenuItems(JMenu m) {
        JMenu m2;
        JMenuItem mi;
        JRadioButtonMenuItem cmi;


        mi = new JMenuItem(labels.getString("startPlayer"));
        mi.setMnemonic(labels.getMnemonic("startPlayerMnem"));
        mi.setAccelerator(labels.getKeyStroke("startPlayerAcc"));
        mi.setActionCommand("start");
        mi.addActionListener(this);
        m.add(mi);
        mi = new JMenuItem(labels.getString("stopPlayer"));
        mi.setMnemonic(labels.getMnemonic("stopPlayerMnem"));
        mi.setAccelerator(labels.getKeyStroke("stopPlayerAcc"));
        mi.setActionCommand("stop");
        mi.addActionListener(this);
        m.add(mi);
        m.add(autoPlayItem);
        m.addSeparator();

        mi = new JMenuItem(labels.getString("halfSize"));
        mi.setMnemonic(labels.getMnemonic("halfSizeMnem"));
        mi.setAccelerator(labels.getKeyStroke("halfSizeAcc"));
        mi.setActionCommand("halfSize");
        mi.addActionListener(this);
        m.add(mi);
        mi = new JMenuItem(labels.getString("normalSize"));
        mi.setMnemonic(labels.getMnemonic("normalSizeMnem"));
        mi.setAccelerator(labels.getKeyStroke("normalSizeAcc"));
        mi.setActionCommand("normalSize");
        mi.addActionListener(this);
        m.add(mi);
        mi = new JMenuItem(labels.getString("doubleSize"));
        mi.setMnemonic(labels.getMnemonic("doubleSizeMnem"));
        mi.setAccelerator(labels.getKeyStroke("doubleSizeAcc"));
        mi.setActionCommand("doubleSize");
        mi.addActionListener(this);
        m.add(mi);
        mi = new JMenuItem(labels.getString("fillScreen"));
        mi.setMnemonic(labels.getMnemonic("fillScreenMnem"));
        mi.setAccelerator(labels.getKeyStroke("fillScreenAcc"));
        mi.setActionCommand("fillScreen");
        mi.addActionListener(this);
        m.add(mi);
        m.addSeparator();

        m2 = new JMenu(labels.getString("imageAspect"));
        m2.setMnemonic(labels.getMnemonic("imageAspectMnem"));
        m2.add(ignoreImageAspectItem);
        m2.add(exactImageAspectItem);
        m.add(m2);

        m2 = new JMenu(labels.getString("pixelAspect"));
        m2.setMnemonic(labels.getMnemonic("pixelAspectMnem"));
        m2.add(ignorePixelAspectItem);
        m2.add(exactPixelAspectItem);
        m2.add(anamorphPixelAspectItem);
        m.add(m2);
    }

    protected void updateViewProperties() {
        super.updateViewProperties();
        updateImageAspect();
        updateInterpolation();
        updatePixelAspect();
        updateBackground();
        //updateScaleFactor();
        updateAudioEnabled();
        updateLoop();
        updatePlayEveryFrame();
        updateInitialScaleFactor();
    }

    protected void updateApplicationProperties() {
        super.updateApplicationProperties();
        updateAutoPlay();
        updateShowScrollbars();
    }

    private void updateBackground() {
        String newValue = getViewProperties().getProperty("background", "black");
        if (newValue.equals("white")) {
            videoPanel.setBackground(Color.white);
            videoPanel.setForeground(Color.black);
            videoPanel.setTexture(null);
            backgroundWhiteItem.setSelected(true);
        } else if (newValue.equals("checkerboard")) {
            videoPanel.setBackground(new Color(176, 176, 176));
            videoPanel.setForeground(Color.black);
            videoPanel.setTexture(checkerboardTexture);
            backgroundCheckerboardItem.setSelected(true);
        } else {
            videoPanel.setBackground(Color.black);
            videoPanel.setForeground(Color.white);
            videoPanel.setTexture(null);
            backgroundBlackItem.setSelected(true);
        }
        repaint();
    }

    protected void updateInterpolation() {
    }

    protected void updateImageAspect() {
        if (hasScrollPane()) {
            videoLayoutConstraints.setScaleToFit(false);
        } else {
            videoLayoutConstraints.setScaleToFit(true);
            String newValue = getViewProperties().getProperty("imageAspect", "exact");
            if (newValue.equals("exact")) {
                videoLayoutConstraints.setMaintainAspectRatio(true);
                exactImageAspectItem.setSelected(true);
            } else {
                videoLayoutConstraints.setMaintainAspectRatio(false);
                ignoreImageAspectItem.setSelected(true);
            }
        }
        videoPanel.invalidate();
        videoPanel.revalidate();
    }

    protected void updatePixelAspect() {
        String newValue = getViewProperties().getProperty("pixelAspect", "exact");
        int policy;
        if (newValue.equals("ignore")) {
            policy = ImagePanel.IGNORE_PIXEL_ASPECT;
            videoLayoutConstraints.setPixelAspectRatio(1d);
        } else if (newValue.equals("rounded")) {
            policy = ImagePanel.ROUNDED_PIXEL_ASPECT;
            videoLayoutConstraints.setPixelAspectRatio(1d);
        } else if (newValue.equals("anamorph")) {
            policy = ImagePanel.ANAMORPH_PIXEL_ASPECT;
            videoLayoutConstraints.setPixelAspectRatio(16d / 9d * 4d / 5d);
        } else {
            policy = ImagePanel.EXACT_PIXEL_ASPECT;
            videoLayoutConstraints.setPixelAspectRatio(1d);
        }

        //imagePanel_.setPixelAspectPolicy(policy);
        ignorePixelAspectItem.setSelected(policy == ImagePanel.IGNORE_PIXEL_ASPECT);
        multipliedPixelAspectItem.setSelected(policy == ImagePanel.ROUNDED_PIXEL_ASPECT);
        exactPixelAspectItem.setSelected(policy == ImagePanel.EXACT_PIXEL_ASPECT);
        anamorphPixelAspectItem.setSelected(policy == ImagePanel.ANAMORPH_PIXEL_ASPECT);

        videoPanel.invalidate();
        videoPanel.revalidate();
    }

    protected void updateInitialScaleFactor() {
        String newValue = getViewProperties().getProperty("initialScaleFactor", "normal");
        if (newValue.equals("half")) {
            setViewSize(HALF_SIZE);
        } else if (newValue.equals("normal")) {
            setViewSize(NORMAL_SIZE);
        } else if (newValue.equals("double")) {
            setViewSize(DOUBLE_SIZE);
        } else if (newValue.equals("fillScreen")) {
            setViewSize(SCREEN_SIZE);
        }
    }

    protected void updateAutoPlay() {
        autoPlayItem.setSelected(isAutoPlay());
    }

    protected boolean isAutoPlay() {
        return getApplicationProperties().getProperty("autoPlay", "false").equals("true");
    }

    protected void updateShowScrollbars() {
        if (hasScrollPane()) {
            remove(videoPanel);
            updateImageAspect();
            add(scrollPane, BorderLayout.CENTER);
            scrollPane.setViewportView(videoPanel);
            scrollPane.invalidate();
        } else {
            remove(scrollPane);
            updateImageAspect();
            add(videoPanel, BorderLayout.CENTER);
            videoPanel.invalidate();
        }
        showScrollbarsItem.setSelected(hasScrollPane());
        invalidate();
        validate();
        repaint();
    }

    public boolean hasScrollPane() {
        return getApplicationProperties().getProperty("showScrollbars", "false").equals("true");
    }

    public void setViewSize(int size) {
        PropertyModel p = getViewProperties();
        ScaleFactorConstraints old = videoLayoutConstraints;

        switch (size) {
            case HALF_SIZE:
                videoLayoutConstraints.setConstraints(0.5, 0.5, videoLayoutConstraints.isMaintainAspectRatio());
                p.put("initialScaleFactor", "half");
                break;

            case NORMAL_SIZE:
                videoLayoutConstraints.setConstraints(1.0, 1.0, videoLayoutConstraints.isMaintainAspectRatio());
                p.put("initialScaleFactor", "normal");
                break;

            case DOUBLE_SIZE:
                videoLayoutConstraints.setConstraints(2.0, 2.0, videoLayoutConstraints.isMaintainAspectRatio());
                p.put("initialScaleFactor", "double");
                break;

            case SCREEN_SIZE:
                p.put("initialScaleFactor", "fillScreen");
                if (getVideoComponent() != null && isDisplayable()) {
                    Dimension id = getVideoComponent().getPreferredSize();
                    Dimension sd = getGraphicsConfiguration().getBounds().getSize();
                    Insets screenInsets = getToolkit().getScreenInsets(getGraphicsConfiguration());
                    Insets insets = getRootPane().getParent().getInsets();
                    insets.top += screenInsets.top;
                    insets.left += screenInsets.left;
                    insets.bottom += screenInsets.bottom;
                    insets.right += screenInsets.right;
                    Rectangle r = getRootPane().getContentPane().getBounds();
                    insets.top += r.y;
                    insets.left += r.x;
                    insets.bottom += getControllerComponent().getPreferredSize().height;

                    double factor = Math.min(
                            (sd.width - (insets.left + insets.right)) / (double) id.width,
                            (sd.height - (insets.top + insets.bottom)) / (double) id.height);

                    //videoPanel.setScaleFactor(factor);
                    videoLayoutConstraints.setConstraints(factor, factor, videoLayoutConstraints.isMaintainAspectRatio());
                    updateScaleFactor();
                }

        }
    }

    protected void updateScaleFactor() {
        Component c = getVideoComponent();
        if (c != null) {
            Dimension p = c.getPreferredSize();
            Dimension s = c.getSize();
            double sf = Math.min((s.width / (double) p.width), (s.height / (double) p.height));
            getViewProperties().put("title", " as Video @ " + (int) Math.round(100 * sf) + "%");
        }
    }

    protected void updatePlayEveryFrame() {
        boolean b = getViewProperties().getProperty("playEveryFrame", "false").equals("true");
        playEveryFrameItem.setSelected(b);
    }

    protected void updateLoop() {
        boolean b = getViewProperties().getProperty("loop", "false").equals("true");
        loopItem.setSelected(b);
    }

    protected void updateAudioEnabled() {
        boolean b = getViewProperties().getProperty("audioEnabled", "true").equals("true");
        audioEnabledItem.setSelected(b);
    }

    /**
     * This method should be abstract but the form editor in NetBeans 3.4
     * does not suport abstract classes.
     */
    public void startPlayer() {
        throw new InternalError("Subclass responsibility");
    }

    /**
     * This method should be abstract but the form editor in NetBeans 3.4
     * does not suport abstract classes.
     */
    public void stopPlayer() {
        throw new InternalError("Subclass responsibility");
    }

    public void actionPerformed(ActionEvent event) {
        String actionCommand = event.getActionCommand();

        if (actionCommand.equals("start")) {
            startPlayer();
        } else if (actionCommand.equals("stop")) {
            stopPlayer();
        } else if (actionCommand.equals("halfSize")) {
            setViewSize(HALF_SIZE);
            packParent();
        } else if (actionCommand.equals("normalSize")) {
            setViewSize(NORMAL_SIZE);
            packParent();
        } else if (actionCommand.equals("doubleSize")) {
            setViewSize(DOUBLE_SIZE);
            packParent();
        } else if (actionCommand.equals("fillScreen")) {
            setViewSize(SCREEN_SIZE);
            packParent();
        }
    }

    public void itemStateChanged(ItemEvent event) {
        Object source = event.getSource();
        PropertyModel p = getViewProperties();

        // Handle selection events only for JRadioButtonMenuItem's
        if (event.getStateChange() == ItemEvent.SELECTED) {
            if (source == ignorePixelAspectItem) {
                p.put("pixelAspect", "ignore");
            } else if (source == multipliedPixelAspectItem) {
                p.put("pixelAspect", "rounded");
            } else if (source == exactPixelAspectItem) {
                p.put("pixelAspect", "exact");
            } else if (source == anamorphPixelAspectItem) {
                p.put("pixelAspect", "anamorph");

            } else if (source == ignoreImageAspectItem) {
                p.put("imageAspect", "ignore");
            } else if (source == exactImageAspectItem) {
                p.put("imageAspect", "exact");

            } else if (source == backgroundBlackItem) {
                p.put("background", "black");
            } else if (source == backgroundWhiteItem) {
                p.put("background", "white");
            } else if (source == backgroundCheckerboardItem) {
                p.put("background", "checkerboard");


            } else if (source == nearestNeighborItem) {
                p.put("interpolation", "nearestNeighbor");
            } else if (source == bilinearItem) {
                p.put("interpolation", "bilinear");
            } else if (source == bicubicItem) {
                p.put("interpolation", "bicubic");
            }
        }

        // Handle selection and deselection for JCheckBoxMenuItem's
        if (source == autoPlayItem) {
            getApplicationProperties().put(
                    "autoPlay", String.valueOf(autoPlayItem.getState()));
        } else if (source == showScrollbarsItem) {
            getApplicationProperties().put(
                    "showScrollbars", String.valueOf(showScrollbarsItem.getState()));
        } else if (source == playEveryFrameItem) {
            p.put(
                    "playEveryFrame",
                    String.valueOf(event.getStateChange() == ItemEvent.SELECTED));
        } else if (source == loopItem) {
            p.put(
                    "loop",
                    String.valueOf(event.getStateChange() == ItemEvent.SELECTED));
        } else if (source == audioEnabledItem) {
            boolean b = event.getStateChange() == ItemEvent.SELECTED;
            p.put("audioEnabled", String.valueOf(b));
        }
    }

    public void propertyChange(PropertyChangeEvent event) {
        super.propertyChange(event);
        String key = event.getPropertyName();

        Object source = event.getSource();
        if (source == getViewProperties()) {
            Object newValue = event.getNewValue();
            if (key.equals("scaleFactor")) {
                updateScaleFactor();
            } else if (key.equals("imageAspect")) {
                updateImageAspect();
            } else if (key.equals("interpolation")) {
                updateInterpolation();
            } else if (key.equals("pixelAspect")) {
                updatePixelAspect();
            } else if (key.equals("background")) {
                updateBackground();
            } else if (key.equals("playEveryFrame")) {
                updatePlayEveryFrame();
            } else if (key.equals("loop")) {
                updateLoop();
            } else if (key.equals("audioEnabled")) {
                updateAudioEnabled();
            }
        } else if (source == getApplicationProperties()) {
            if (key.equals("showScrollbars")) {
                updateShowScrollbars();
            } else if (key.equals("autoPlay")) {
                updateAutoPlay();
            }
        }
    }

    protected void packParent() {
        Container container = getParent();
        if (container != null) {
            while (container.getParent() != null) {
                container.invalidate();
                container = container.getParent();
            }
        }
        if (container != null && container instanceof JFrame) {
            ((JFrame) container).pack();
        }
    }

    public void init() {
        updateInitialScaleFactor();
    //updateInitialPixelAspect();
    }

    /**
     * This method adds the given drop target listener to
     * all components of the viewer.
     */
    public void addDropTargetListener(DropTargetListener listener) {
        new DropTarget(this, listener);
        new DropTarget(videoPanel, listener);
        new DropTarget(scrollPane, listener);
        new DropTarget(controllerPanel, listener);
        new DropTarget(strutPanel, listener);
        new DropTarget(southPanel, listener);
        new DropTarget(getVideoComponent(), listener);
        new DropTarget(getControllerComponent(), listener);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    private void initComponents() {//GEN-BEGIN:initComponents
        southPanel = new javax.swing.JPanel();
        strutPanel = new javax.swing.JPanel();
        controllerPanel = new javax.swing.JPanel();

        setLayout(new java.awt.BorderLayout());

        southPanel.setLayout(new java.awt.BorderLayout());

        strutPanel.setPreferredSize(new java.awt.Dimension(15, 0));
        southPanel.add(strutPanel, java.awt.BorderLayout.EAST);

        controllerPanel.setLayout(new java.awt.BorderLayout());

        southPanel.add(controllerPanel, java.awt.BorderLayout.CENTER);

        add(southPanel, java.awt.BorderLayout.SOUTH);

    }//GEN-END:initComponents
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JPanel controllerPanel;
    private javax.swing.JPanel southPanel;
    private javax.swing.JPanel strutPanel;
    // End of variables declaration//GEN-END:variables
    protected static class VideoPanel extends JPanel {

        private BufferedImage texture;

        public VideoPanel() {
            setOpaque(true);

            // Experimental:
            setDoubleBuffered(false);
        }

        public void setTexture(BufferedImage texture) {
            this.texture = texture;
            repaint();
        }

        public void paintComponent(Graphics gr) {
            super.paintComponent(gr);

            Graphics2D g = (Graphics2D) gr;

            // Draw a border around each component on the panel.
            g.setColor(getForeground());
            Component[] components = getComponents();
            for (int i = 0; i < components.length; i++) {
                if (components[i].isVisible()) {
                    Rectangle r = components[i].getBounds();
                    g.drawRect(r.x - 1, r.y - 1, r.width + 1, r.height + 1);
                    if (texture != null) {
                        Paint p = new TexturePaint(texture, new Rectangle(r.x, r.y, texture.getWidth(), texture.getHeight()));
                        g.setPaint(p);
                        g.fillRect(r.x, r.y, r.width, r.height);
                    }
                }
            }
        }
    }
}
