/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: EditKeyBinding.java
 *
 * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.sun.electric.tool.user.dialogs;

import com.sun.electric.tool.user.KeyBindingManager;
import com.sun.electric.tool.user.menus.EMenuBar;
import com.sun.electric.tool.user.menus.EMenuItem;
import com.sun.electric.tool.user.ui.KeyBindings;
import com.sun.electric.tool.user.ui.KeyStrokePair;
import com.sun.electric.tool.user.ui.TopLevel;

import java.awt.event.InputEvent;
import java.util.Iterator;
import java.util.List;

import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListModel;

/**
 * Class to manage binding of keys to pulldown menus.
 */
public class EditKeyBinding extends EDialog {

    /** The MenuItem we are adding a key binding to */  private EMenuItem menuItem;
    /** The MenuBar that item is a part of */           private EMenuBar menuBar;
    /** Last stroke1 input */                           private KeyStroke key1;
    /** Last stroke2 input */                           private KeyStroke key2;

    /**
     * Creates new form EditKeyBinding.
     * @param item the menu item which we are adding a Key Binding to.
     */
    public EditKeyBinding(EMenuItem item, EMenuBar menuBar) {
        super(null, true);
        menuItem = item;
        this.menuBar = menuBar;
        key1 = null;
        key2 = null;
        initComponents();                   // prebuilt by netbeans
        initDialog();
		finishInitialization();
    }
    
    /** 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
        java.awt.GridBagConstraints gridBagConstraints;

        header = new javax.swing.JLabel();
        stroke1Label = new javax.swing.JLabel();
        stroke1Input = new javax.swing.JTextField();
        jSeparator1 = new javax.swing.JSeparator();
        optionalLabel = new javax.swing.JLabel();
        stroke2Label = new javax.swing.JLabel();
        stroke2Input = new javax.swing.JTextField();
        jSeparator2 = new javax.swing.JSeparator();
        conflictsLabel = new javax.swing.JLabel();
        conflictsList = new javax.swing.JList();
        jSeparator3 = new javax.swing.JSeparator();
        jPanel1 = new javax.swing.JPanel();
        addButton = new javax.swing.JButton();
        cancelButton = new javax.swing.JButton();

        getContentPane().setLayout(new java.awt.GridBagLayout());

        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                closeDialog(evt);
            }
        });

        header.setText("jLabel1");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 6);
        getContentPane().add(header, gridBagConstraints);

        stroke1Label.setText("First stroke:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 6);
        getContentPane().add(stroke1Label, gridBagConstraints);

        stroke1Input.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                stroke1InputKeyPressed(evt);
            }
            public void keyReleased(java.awt.event.KeyEvent evt) {
                stroke1InputKeyReleased(evt);
            }
            public void keyTyped(java.awt.event.KeyEvent evt) {
                stroke1InputKeyTyped(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 0.5;
        gridBagConstraints.insets = new java.awt.Insets(2, 7, 2, 7);
        getContentPane().add(stroke1Input, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 6);
        getContentPane().add(jSeparator1, gridBagConstraints);

        optionalLabel.setText("(Optional):");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 6);
        getContentPane().add(optionalLabel, gridBagConstraints);

        stroke2Label.setText("Second stroke:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 6);
        getContentPane().add(stroke2Label, gridBagConstraints);

        stroke2Input.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                stroke2InputKeyPressed(evt);
            }
            public void keyReleased(java.awt.event.KeyEvent evt) {
                stroke2InputKeyReleased(evt);
            }
            public void keyTyped(java.awt.event.KeyEvent evt) {
                stroke2InputKeyTyped(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 0.5;
        gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 6);
        getContentPane().add(stroke2Input, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 6);
        getContentPane().add(jSeparator2, gridBagConstraints);

        conflictsLabel.setText("Conflicts:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 6;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 6);
        getContentPane().add(conflictsLabel, gridBagConstraints);

        conflictsList.setBorder(new javax.swing.border.EtchedBorder());
        conflictsList.setPreferredSize(new java.awt.Dimension(220, 80));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 7;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 2.0;
        gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 6);
        getContentPane().add(conflictsList, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 8;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 6);
        getContentPane().add(jSeparator3, gridBagConstraints);

        jPanel1.setLayout(new java.awt.GridBagLayout());

        addButton.setText("Add");
        addButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addButtonActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(6, 6, 6, 6);
        jPanel1.add(addButton, gridBagConstraints);

        cancelButton.setText("Cancel");
        cancelButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cancelButtonActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(6, 6, 6, 6);
        jPanel1.add(cancelButton, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 9;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 6);
        getContentPane().add(jPanel1, gridBagConstraints);

        pack();
    }//GEN-END:initComponents

    private void initDialog() {
        String description = menuItem.getDescription();
        header.setText("Add Key Binding for:  "+description);
        pack();
    }

    // ------------------------------------- Actions ------------------------------------

    /** Ignore input key typed events */
    private void stroke2InputKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_stroke2InputKeyTyped
        evt.consume();
    }//GEN-LAST:event_stroke2InputKeyTyped

    /** Ignore input key released events */
    private void stroke2InputKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_stroke2InputKeyReleased
        evt.consume();
    }//GEN-LAST:event_stroke2InputKeyReleased

    /**
     * Respond to key input to text box. Show string in the text box
     * representing that KeyStroke.
     * @param evt the KeyEvent
     */
    private void stroke2InputKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_stroke2InputKeyPressed
        if (!KeyBindingManager.validKeyEvent(evt)) return;
        key2 = KeyStroke.getKeyStrokeForEvent(evt);
        stroke2Input.setText(KeyStrokePair.keyStrokeToString(key2));
        updateConflicts();
        evt.consume();
    }//GEN-LAST:event_stroke2InputKeyPressed

    /** Ignore input key typed events */
    private void stroke1InputKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_stroke1InputKeyTyped
        evt.consume();
    }//GEN-LAST:event_stroke1InputKeyTyped
    
    /** Ignore input key released events */
    private void stroke1InputKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_stroke1InputKeyReleased
        evt.consume();
    }//GEN-LAST:event_stroke1InputKeyReleased

    /**
     * Respond to key input to text box. Show string in the text box
     * representing that KeyStroke.
     * @param evt the KeyEvent
     */
    private void stroke1InputKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_stroke1InputKeyPressed
        if (!KeyBindingManager.validKeyEvent(evt)) return;
        key1 = KeyStroke.getKeyStrokeForEvent(evt);
        stroke1Input.setText(KeyStrokePair.keyStrokeToString(key1));
        addButton.setEnabled(updateConflicts());
        evt.consume();
    }//GEN-LAST:event_stroke1InputKeyPressed

    private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
        setVisible(false);
        dispose();
    }//GEN-LAST:event_cancelButtonActionPerformed

    private void addButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addButtonActionPerformed

        KeyStroke prefixStroke = getStroke(stroke1Input);
        KeyStroke stroke = getStroke(stroke2Input);
        if (prefixStroke == null) {
            dialogBadInput();
            return;
        }

        ListModel model = conflictsList.getModel();
        // if conflicts exist, ask user if they want to remove or leave them (or do nothing)
        int removeConflicts = 1;
        if (model.getSize() != 0) {
            removeConflicts = removeConflictsDialog(model);
            // removeConflicts: 0 to remove all, 1 to leave all conflicts, 2 to cancel (default)
        }
        if (removeConflicts == 2) {
            return;                         // do nothing
        } else if (removeConflicts == 0) {
            List<KeyBindings> conflicts = getConflicts();
            for (KeyBindings k : conflicts) {
                // remove all bindings in k
                for (Iterator<KeyStrokePair> kit = k.getKeyStrokePairs(); kit.hasNext(); ) {
                    KeyStrokePair pair = kit.next();
                    menuBar.removeKeyBinding(k.getActionDesc(), pair);
                }
            }
        }
        // add new key binding
        if (stroke == null) {
            stroke = prefixStroke; prefixStroke = null;
        }
        menuBar.addUserKeyBinding(menuItem, stroke, prefixStroke);
        setVisible(false);
        dispose();
    }//GEN-LAST:event_addButtonActionPerformed

    private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog

    }//GEN-LAST:event_closeDialog


    // --------------------------------- List Box ----------------------------------

    /**
     * Method to check conflicts with previous defined key strokes.
     * @return False if the conflicts can't be generated (alt- definitions).
     */
    private boolean updateConflicts() {
        List<KeyBindings> conflicts = getConflicts();
        if (conflicts == null || conflicts.size() == 0) {
            clearConflictsList();
        } else
        {
	        // create array of strings to display in list box
	        String [] objects = new String[conflicts.size()];
	        for (int i=0; i<conflicts.size(); i++) {
	            KeyBindings k = conflicts.get(i);
	            String s = k.getActionDesc() + "  [ "+k.bindingsToString()+" ]";
	            objects[i] = s;
	        }
	        // update list box
	        conflictsList.setListData(objects);
	        return true;
        }

        // warn if an ALT key conflicts with a pulldown menu
        if ((key1.getModifiers() & InputEvent.ALT_MASK) != 0)
        {
			TopLevel top = TopLevel.getCurrentJFrame();
			EMenuBar menuBar = top.getEMenuBar();
			for (EMenuItem menu: menuBar.getItems())
			{
				char reservedAlt = menu.getMnemonicChar();
				if (reservedAlt == 0) continue;
				KeyStroke ksRes = KeyStroke.getKeyStroke(reservedAlt, InputEvent.ALT_MASK);
				if (ksRes == key1)
				{
			        String [] objects = new String[1];
		            objects[0] = "Pulldown menu: " + menu.getText();
                    System.out.println("Stroke is pre-reserved for \"" + objects[0] + "\" and it can't be modified.");
                    conflictsList.setListData(objects);
			        return false;
				}
			}
        }
//        // check for reserved keystrokes (doesn't work)
//        KeyStroke key1 = getStroke(stroke1Input);
//        Keymap map = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
//        for ( ; map != null; map = map.getResolveParent())
//        {
//            KeyStroke[] keys = map.getBoundKeyStrokes();
//            for (int i=0; i<keys.length; i++)
//            {
//            	if (keys[i] == key1)
//            	{
//        	        System.out.println(key1 + " IS RESERVED");
//            	}
//            }
//        }
        return true;
    }

    /**
     * Clear conflicts list
     */
    private void clearConflictsList() {
        conflictsList.setListData(new Object [] {});
    }

    /**
     * Get list of KeyBindings that conflict with key combo user
     * has entered into this dialog.
     * @return a list of KeyBindings, or null if none.
     */
    private List<KeyBindings> getConflicts() {
        // grab key combo from input boxes
        KeyStroke key1 = getStroke(stroke1Input);
        if (key1 == null) { clearConflictsList(); return null; }
        KeyStroke key2 = getStroke(stroke2Input);

        // if key2 is null, then key1 is the primary stroke.
        // otherwise, key1 is the prefix stroke, and key2 is the primary stroke
        KeyStroke prefixStroke = key1, stroke = key2;       // assumes key2 non-null
        if (key2 == null) {
            stroke = key1; prefixStroke = null;
        }
        KeyStrokePair pair = KeyStrokePair.getKeyStrokePair(prefixStroke, stroke);
        return KeyBindingManager.getConflictsAllManagers(pair);
    }

    /**
     * Get KeyStroke from input field.
     * @param field a JTextField with text that describes a KeyStroke
     * @return the KeyStroke, or null if invalid text in field.
     */
    private KeyStroke getStroke(JTextField field) {
        String str = field.getText();
        return KeyStrokePair.stringToKeyStroke(str);
    }

    // ------------------------------ Helper Dialogs ------------------------------

    /**
     * Pops up a JOptionPain dialog box to ask user if they want to remove or leave
     * conflicting bindings, or just cancel.
     * @return 0 to remove all, 1 to leave all conflicts, 2 to cancel (default)
     */
    private int removeConflictsDialog(ListModel model) {
        StringBuffer buf = new StringBuffer();
        buf.append("The following conflicts exists. Would you like to remove them?\n");
        for (int i=0; i<model.getSize(); i++) {
            buf.append(model.getElementAt(i).toString()+"\n");
        }
        Object [] options = {"Remove All", "Leave All", "Cancel"};
        int n = JOptionPane.showOptionDialog(this,
                buf.toString(),
                "Conflicts Exist", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
                null, options, options[2]);
        return n;
    }

    private void dialogBadInput() {
        JOptionPane.showMessageDialog(null, "I cannot bind to the First Stroke key you have specified.  Sorry",
                "Invalid Input", JOptionPane.WARNING_MESSAGE);
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton addButton;
    private javax.swing.JButton cancelButton;
    private javax.swing.JLabel conflictsLabel;
    private javax.swing.JList conflictsList;
    private javax.swing.JLabel header;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JSeparator jSeparator1;
    private javax.swing.JSeparator jSeparator2;
    private javax.swing.JSeparator jSeparator3;
    private javax.swing.JLabel optionalLabel;
    private javax.swing.JTextField stroke1Input;
    private javax.swing.JLabel stroke1Label;
    private javax.swing.JTextField stroke2Input;
    private javax.swing.JLabel stroke2Label;
    // End of variables declaration//GEN-END:variables
    
}
