/*

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Batik" and  "Apache Software Foundation" must  not  be
    used to  endorse or promote  products derived from  this software without
    prior written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation. For more  information on the
 Apache Software Foundation, please see <http://www.apache.org/>.

*/

package org.apache.batik.util.gui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.StringTokenizer;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.apache.batik.util.gui.resource.ActionMap;
import org.apache.batik.util.gui.resource.ButtonFactory;
import org.apache.batik.util.gui.resource.MissingListenerException;
import org.apache.batik.util.gui.resource.ResourceManager;

/**
 * This class represents a panel to edit/add/remove CSS media.
 *
 * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
 * @version $Id: CSSMediaPanel.java,v 1.4 2003/08/09 16:58:44 deweese Exp $
 */
public class CSSMediaPanel extends JPanel implements ActionMap {

    /**
     * The resource file name
     */
    protected final static String RESOURCES =
        "org.apache.batik.util.gui.resources.CSSMediaPanel";

    /**
     * The resource bundle
     */
    protected static ResourceBundle bundle;

    /**
     * The resource manager
     */
    protected static ResourceManager resources;

    static {
        bundle = ResourceBundle.getBundle(RESOURCES, Locale.getDefault());
        resources = new ResourceManager(bundle);
    }

    /**
     * The button to remove a CSS medium from the list.
     */
    protected JButton removeButton;

    /**
     * The button to add a CSS medium from the list.
     */
    protected JButton addButton;

    /**
     * The button to clear the CSS media list.
     */
    protected JButton clearButton;

    /**
     * The list that represents the CSS media.
     */
    protected DefaultListModel listModel = new DefaultListModel();

    /**
     * The list that represents the CSS media.
     */
    protected JList mediaList;

    /**
     * Constructs a new panel to edit CSS media.
     */
    public CSSMediaPanel() {
	super(new GridBagLayout());

	listeners.put("AddButtonAction", new AddButtonAction());
	listeners.put("RemoveButtonAction", new RemoveButtonAction());
	listeners.put("ClearButtonAction", new ClearButtonAction());

	setBorder(BorderFactory.createTitledBorder
		  (BorderFactory.createEtchedBorder(),
		   resources.getString("Panel.title")));
	    
	ExtendedGridBagConstraints constraints =
	    new ExtendedGridBagConstraints();

	constraints.insets = new Insets(5, 5, 5, 5);

	mediaList = new JList();
	mediaList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
	mediaList.setModel(listModel);
	mediaList.addListSelectionListener(new MediaListSelectionListener());
	listModel.addListDataListener(new MediaListDataListener());

	JScrollPane scrollPane = new JScrollPane();
	scrollPane.setBorder(BorderFactory.createLoweredBevelBorder());
	constraints.weightx = 1.0;
	constraints.weighty = 1.0;
	constraints.fill = GridBagConstraints.BOTH;
	constraints.setGridBounds(0, 0, 1, 3);
	scrollPane.getViewport().add(mediaList);
	add(scrollPane, constraints);

	ButtonFactory bf = new ButtonFactory(bundle, this);
	constraints.weightx = 0;
	constraints.weighty = 0;
	constraints.fill = GridBagConstraints.HORIZONTAL;
	constraints.anchor = GridBagConstraints.NORTH;

	addButton = bf.createJButton("AddButton");
	constraints.setGridBounds(1, 0, 1, 1);
	add(addButton, constraints);

	removeButton = bf.createJButton("RemoveButton");
	constraints.setGridBounds(1, 1, 1, 1);
	add(removeButton, constraints);

	clearButton = bf.createJButton("ClearButton");
	constraints.setGridBounds(1, 2, 1, 1);
	add(clearButton, constraints);

	updateButtons();
    }

    /**
     * Updates the button states.
     */
    protected void updateButtons() {
	removeButton.setEnabled(!mediaList.isSelectionEmpty());
	clearButton.setEnabled(!listModel.isEmpty());
    }

    /**
     * Sets the list of media to edit.
     *
     * @param mediaList the list of media to edit
     */
    public void setMedia(List mediaList) {
	listModel.removeAllElements();
	Iterator iter = mediaList.iterator();
	while (iter.hasNext()) {
	    listModel.addElement(iter.next());
	}
    }

    /**
     * Sets the list of media to edit to the specified media list (separated by
     * space).
     *
     * @param media the media separated by space 
     */
    public void setMedia(String media) {
	listModel.removeAllElements();
	StringTokenizer tokens = new StringTokenizer(media, " ");
	while (tokens.hasMoreTokens()) {
	    listModel.addElement(tokens.nextToken());
	}
    }
    
    /**
     * Returns the list of media.
     */
    public List getMedia() {
	List media = new ArrayList(listModel.size());
	Enumeration enum = listModel.elements();
	while (enum.hasMoreElements()) {
	    media.add(enum.nextElement());
	}
	return media;
    }

    /**
     * Returns the media list as a string separated by space.
     */
    public String getMediaAsString() {
	StringBuffer buffer = new StringBuffer();
	Enumeration enum = listModel.elements();
	while (enum.hasMoreElements()) {
	    buffer.append((String)enum.nextElement());
	    buffer.append(" ");
	}
	return buffer.toString();
    }

    /**
     * Brings up a modal dialog to edit/add/remove CSS media.
     *
     * @param parent the parent of this dialog
     * @param title the title of this dialog
     */
    public static int showDialog(Component parent, String title) {
	return showDialog(parent, title, "");
    }

    /**
     * Brings up a modal dialog to edit/add/remove CSS media.
     *
     * @param parent the parent of this dialog
     * @param title the title of this dialog
     * @param mediaList the list of media
     */
    public static int showDialog(Component parent, 
				 String title, 
				 List mediaList) {
	Dialog dialog = new Dialog(parent, title, mediaList);
	dialog.setModal(true);
	dialog.pack();
	dialog.show();
	return dialog.getReturnCode();
    }

    /**
     * Brings up a modal dialog to edit/add/remove CSS media.
     *
     * @param parent the parent of this dialog
     * @param title the title of this dialog
     * @param media the list of media
     */
    public static int showDialog(Component parent, 
				 String title, 
				 String media) {
	Dialog dialog = new Dialog(parent, title, media);
	dialog.setModal(true);
	dialog.pack();
	dialog.show();
	return dialog.getReturnCode();
    }

    /**
     * The map that contains the listeners
     */
    protected Map listeners = new HashMap();
    
    /**
     * Returns the action associated with the given string or null on error
     *
     * @param key the key mapped with the action to get
     * @throws MissingListenerException if the action is not found 
     */
    public Action getAction(String key) throws MissingListenerException {
	return (Action)listeners.get(key);
    }

    /**
     * The action associated with the 'Add' button
     */
    protected class AddButtonAction extends AbstractAction {
	public void actionPerformed(ActionEvent e) {
	    AddMediumDialog dialog = new AddMediumDialog(CSSMediaPanel.this);
	    dialog.pack();
	    dialog.show();

	    if ((dialog.getReturnCode() == AddMediumDialog.CANCEL_OPTION) ||
		(dialog.getMedium() == null)) {
		return;
	    }

	    String medium = dialog.getMedium().trim();
	    if (medium.length() == 0 || listModel.contains(medium)) {
		return;
	    }

	    for (int i = 0; i < listModel.size() && medium != null; ++i) {
		String s = (String)listModel.getElementAt(i);
		int c = medium.compareTo(s);
		if (c == 0) {
		    medium = null;
		} else if (c < 0) {
		    listModel.insertElementAt(medium, i);
		    medium = null;
		}
	    }
	    if (medium != null) {
		listModel.addElement(medium);
	    }
	}
    }

    /**
     * The action associated with the 'Remove' button
     */
    protected class RemoveButtonAction extends AbstractAction {
	public void actionPerformed(ActionEvent e) {
	    int index = mediaList.getSelectedIndex(); 
	    mediaList.clearSelection();
	    if (index >= 0) {
		listModel.removeElementAt(index);
	    }
	}
    }

    /**
     * The action associated with the 'Clear' button
     */
    protected class ClearButtonAction extends AbstractAction {
	public void actionPerformed(ActionEvent e) {
	    mediaList.clearSelection();
	    listModel.removeAllElements();
	}
    }

    /**
     * To manage selection modifications
     */
    protected class MediaListSelectionListener 
	implements ListSelectionListener {

	public void valueChanged(ListSelectionEvent e) {
	    updateButtons();
	}
    }

    /**
     * To manage data modifications in the media list.
     */
    protected class MediaListDataListener implements ListDataListener {
	
	public void contentsChanged(ListDataEvent e) {
	    updateButtons();
	}
	
	public void intervalAdded(ListDataEvent e) {
	    updateButtons();
	}
	
	public void intervalRemoved(ListDataEvent e) {
	    updateButtons();
	}
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * A dialog to add a new CSS medium.
     */
    public static class AddMediumDialog extends JDialog implements ActionMap {

	/**
	 * The return value if 'OK' is chosen.
	 */
	public final static int OK_OPTION = 0;
	
	/**
	 * The return value if 'Cancel' is chosen.
	 */
	public final static int CANCEL_OPTION = 1;

	/**
	 * The new medium.
	 */
	protected JComboBox medium;
	
	/**
	 * The return code.
	 */
	protected int returnCode;

	/**
	 * Constructs a new AddMediumDialog.
	 *
	 * @param parent the parent of this dialog
	 */
	public AddMediumDialog(Component parent) {
	    super(JOptionPane.getFrameForComponent(parent),
		  resources.getString("AddMediumDialog.title"));
	    setModal(true);

	    listeners.put("OKButtonAction", new OKButtonAction());
	    listeners.put("CancelButtonAction", new CancelButtonAction());

	    getContentPane().add(createContentPanel(), BorderLayout.CENTER);
	    getContentPane().add(createButtonsPanel(), BorderLayout.SOUTH);
	}

	/**
	 * Returns the medium that might be added or null if any.
	 */
	public String getMedium() {
	    return (String)medium.getSelectedItem();
	}

	/**
	 * Returns the panel to enter a new CSS medium.
	 */
	protected Component createContentPanel() {
	    JPanel panel = new JPanel(new BorderLayout());
	    panel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
	    panel.add(new JLabel(resources.getString("AddMediumDialog.label")),
		      BorderLayout.WEST);

	    medium = new JComboBox();
	    medium.setEditable(true);
	    String media = resources.getString("Media.list");
	    StringTokenizer tokens = new StringTokenizer(media, " ");
	    while (tokens.hasMoreTokens()) {
		medium.addItem(tokens.nextToken());
	    }
	    panel.add(medium, BorderLayout.CENTER);
	    return panel;
	}

	/**
	 * Returns the button panel.
	 */
	protected Component createButtonsPanel() {
	    JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
	    ButtonFactory bf = new ButtonFactory(bundle, this);
	    panel.add(bf.createJButton("OKButton"));
	    panel.add(bf.createJButton("CancelButton"));
	    return panel;
	}

	/**
	 * Returns the code that describes how the dialog has been closed (OK or
	 * CANCEL).
	 */
	public int getReturnCode() {
	    return returnCode;
	}

	/**
	 * The map that contains the listeners
	 */
	protected Map listeners = new HashMap();
	
	/**
	 * Returns the action associated with the given string or null on error
	 *
	 * @param key the key mapped with the action to get
	 * @throws MissingListenerException if the action is not found 
	 */
	public Action getAction(String key) throws MissingListenerException {
	    return (Action)listeners.get(key);
	}

	/**
	 * The action associated with the 'OK' button
	 */
	protected class OKButtonAction extends AbstractAction {
	    public void actionPerformed(ActionEvent e) {
		returnCode = OK_OPTION;
		dispose();
	    }
	}
	
	/**
	 * The action associated with the 'Cancel' button
	 */
	protected class CancelButtonAction extends AbstractAction {
	    public void actionPerformed(ActionEvent e) {
		returnCode = CANCEL_OPTION;
		dispose();
	    }
	}
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * A dialog to edit/add/remove CSS media.
     */
    public static class Dialog extends JDialog implements ActionMap {
	
	/**
	 * The return value if 'OK' is chosen.
	 */
	public final static int OK_OPTION = 0;
	
	/**
	 * The return value if 'Cancel' is chosen.
	 */
	public final static int CANCEL_OPTION = 1;
	
	/**
	 * The return code.
	 */
	protected int returnCode;

	/**
	 * Constructs a new Dialog to edit/add/remove CSS media.
	 */
	public Dialog() {
	    this(null, "", "");
	}

	/**
	 * Constructs a new Dialog to edit/add/remove CSS media.
	 *
	 * @param parent the parent of this dialog
	 * @param title the title of this dialog
	 * @param mediaList the media list
	 */
	public Dialog(Component parent, String title, List mediaList) {
	    super(JOptionPane.getFrameForComponent(parent), title);

	    listeners.put("OKButtonAction", new OKButtonAction());
	    listeners.put("CancelButtonAction", new CancelButtonAction());

	    CSSMediaPanel panel = new CSSMediaPanel();
	    panel.setMedia(mediaList);
	    getContentPane().add(panel, BorderLayout.CENTER);
	    getContentPane().add(createButtonsPanel(), BorderLayout.SOUTH);
	}

	/**
	 * Constructs a new Dialog to edit/add/remove CSS media.
	 *
	 * @param parent the parent of this dialog
	 * @param title the title of this dialog
	 * @param media the media list
	 */
	public Dialog(Component parent, String title, String media) {
	    super(JOptionPane.getFrameForComponent(parent), title);

	    listeners.put("OKButtonAction", new OKButtonAction());
	    listeners.put("CancelButtonAction", new CancelButtonAction());

	    CSSMediaPanel panel = new CSSMediaPanel();
	    panel.setMedia(media);
	    getContentPane().add(panel, BorderLayout.CENTER);
	    getContentPane().add(createButtonsPanel(), BorderLayout.SOUTH);
	}
	
	/**
	 * Returns the code that describes how the dialog has been closed (OK or
	 * CANCEL).
	 */
	public int getReturnCode() {
	    return returnCode;
	}
	
	/**
	 * Creates the OK/Cancel buttons panel
	 */
	protected JPanel createButtonsPanel() {
	    JPanel  p = new JPanel(new FlowLayout(FlowLayout.RIGHT));
	    ButtonFactory bf = new ButtonFactory(bundle, this);
	    p.add(bf.createJButton("OKButton"));
	    p.add(bf.createJButton("CancelButton"));
	    return p;
	}

	/**
	 * The map that contains the listeners
	 */
	protected Map listeners = new HashMap();
	
	/**
	 * Returns the action associated with the given string or null on error
	 *
	 * @param key the key mapped with the action to get
	 * @throws MissingListenerException if the action is not found 
	 */
	public Action getAction(String key) throws MissingListenerException {
	    return (Action)listeners.get(key);
	}

	/**
	 * The action associated with the 'OK' button
	 */
	protected class OKButtonAction extends AbstractAction {
	    public void actionPerformed(ActionEvent e) {
		returnCode = OK_OPTION;
		dispose();
	    }
	}
	
	/**
	 * The action associated with the 'Cancel' button
	 */
	protected class CancelButtonAction extends AbstractAction {
	    public void actionPerformed(ActionEvent e) {
		returnCode = CANCEL_OPTION;
		dispose();
	    }
	}
    }

    /**
     * Main - debug -
     */
    public static void main(String [] args) {
	String media = "all aural braille embossed handheld print projection screen tty tv";
	int code = CSSMediaPanel.showDialog(null, "Test", media);
	System.out.println(code);
	System.exit(0);
    }
}
