/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: EditKeyBindings.java
 *
 * Copyright (c) 2003, Static Free Software. 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.dialogs.options.PreferencePanel;
import com.sun.electric.tool.user.menus.EMenu;
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 java.util.ArrayList;
import java.util.Iterator;

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;


/**
 * Class to manage bindings of keystrokes to pulldown menus.
 */
public class EditKeyBindings extends PreferencePanel implements TreeSelectionListener {

    /** MenuBarGroup for building dialog tree */            EMenuBar menuBar;

    /** class to encapsulate a tree node for displaying key bindings.
     * The toString() method is overridden to show the key binding next to the
     * command name.  This class encapsulates both JMenuItem and Menu, note
     * that both extend JMenuItem.
     */
    private class KeyBoundTreeNode
    {
        private EMenuItem menuItem;

        KeyBoundTreeNode(EMenuItem menuItem) {
            this.menuItem = menuItem;
        }

        public EMenuItem getMenuItem() { return menuItem; }

        /**
         * Convert to String to show on dialog tree
         */
        public String toString() {
            if (menuItem != EMenuItem.SEPARATOR) {
                StringBuffer buf = new StringBuffer(menuItem.getDescription());
                KeyBindings bindings = menuBar.getKeyBindings(menuItem);
                if (bindings == null) return buf.toString();
                Iterator it = bindings.getKeyStrokePairs();
                if (!it.hasNext()) return buf.toString();
                buf.append("   [ "+bindings.bindingsToString()+" ]");
                return buf.toString();
            }
            return "---------------";               // separator
        }
    }

    /** Creates new form EditKeyBindings */
    public EditKeyBindings(EMenuBar menuBar, java.awt.Frame parent, boolean modal) {
		super(parent, modal);
        this.menuBar = menuBar;

        initComponents();
        buildCommandsTree();
    }

    public EditKeyBindings(EMenuBar menuBar, PreferencesFrame parent, boolean modal) {
		super(parent, modal);
        this.menuBar = menuBar;

        initComponents();
        buildCommandsTree();
    }

    // -------------------------- Preference Panel Stuff -----------------------

    public JPanel getUserPreferencesPanel() {
        return editKeyBindingsPanel;
    }

    public String getName() { return "Key Bindings"; }

	/**
	 * Method called when the factory reset is requested for just this panel.
	 * @return true if the panel can be reset "in place" without redisplay.
	 */
	public boolean resetThis()
	{
        menuBar.resetAllKeyBindings();

        // update tree view
        DefaultTreeModel model = (DefaultTreeModel)commandsTree.getModel();
        model.reload();
        EMenuItem item = getSelectedMenuItem();
        updateListBox(item);
		return true;
	}

	public void reset()
    {
        menuBar.resetAllKeyBindings();
    }

    // -------------------------------------------------------------------------

    /** 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.
     */
    // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        editKeyBindingsPanel = new javax.swing.JPanel();
        jPanel1 = new javax.swing.JPanel();
        jScrollPane1 = new javax.swing.JScrollPane();
        commandsTree = new javax.swing.JTree();
        jPanel2 = new javax.swing.JPanel();
        add = new javax.swing.JButton();
        remove = new javax.swing.JButton();
        resetitem = new javax.swing.JButton();
        jPanel4 = new javax.swing.JPanel();
        jLabel1 = new javax.swing.JLabel();
        bindingsJList = new javax.swing.JList();

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

        setTitle("Edit Key Bindings");
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                exitForm(evt);
            }
        });

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

        jPanel1.setLayout(new java.awt.GridLayout(1, 0));

        jPanel1.setPreferredSize(new java.awt.Dimension(350, 363));
        jScrollPane1.setViewportView(commandsTree);

        jPanel1.add(jScrollPane1);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        editKeyBindingsPanel.add(jPanel1, gridBagConstraints);

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

        jPanel2.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        add.setText("Add");
        add.setToolTipText("add a shortcut");
        add.setPreferredSize(new java.awt.Dimension(68, 28));
        add.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(2, 4, 2, 4);
        jPanel2.add(add, gridBagConstraints);

        remove.setText("Remove");
        remove.setToolTipText("remove a shortcut");
        remove.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(2, 4, 2, 4);
        jPanel2.add(remove, gridBagConstraints);

        resetitem.setText("Reset");
        resetitem.setToolTipText("reset to default setting");
        resetitem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                resetitemActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(2, 4, 2, 4);
        jPanel2.add(resetitem, gridBagConstraints);

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

        jLabel1.setText("Shortcuts:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
        jPanel4.add(jLabel1, gridBagConstraints);

        bindingsJList.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        bindingsJList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPanel4.add(bindingsJList, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridheight = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        jPanel2.add(jPanel4, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        editKeyBindingsPanel.add(jPanel2, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        getContentPane().add(editKeyBindingsPanel, gridBagConstraints);

        pack();
    }// </editor-fold>//GEN-END:initComponents

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

    /**
     * Resets a menu item back to its default key bindings
     * @param evt the event
     */
    private void resetitemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_resetitemActionPerformed
        // get currently selected node
        EMenuItem item = getSelectedMenuItem();
        if (item == null || item == EMenuItem.SEPARATOR || item instanceof EMenu) return;

        // reset item to default bindings
        menuBar.resetKeyBindings(item);

        // update tree view and list box
        DefaultTreeModel model = (DefaultTreeModel)commandsTree.getModel();
        model.reload(getSelectedTreeNode());
        updateListBox(item);
    }//GEN-LAST:event_resetitemActionPerformed

    /**
     * Remove a key binding from the menu item
     * @param evt the event
     */
    private void removeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeActionPerformed
        // get currently selected node
        EMenuItem item = getSelectedMenuItem();
        if (item == null || item == EMenuItem.SEPARATOR || item instanceof EMenu) {
            JOptionPane.showMessageDialog(null, "Please select a menu item first", "Error", JOptionPane.ERROR_MESSAGE);
            return;
        }

        // get selected key binding
        KeyStrokePair pair = getListBoxSelected();
        if (pair == null) {
            JOptionPane.showMessageDialog(null, "Please select the shortcut from the list to remove", "Error", JOptionPane.ERROR_MESSAGE);
            return;
        }

        // remove it and update view
        menuBar.removeKeyBinding(item.getDescription(), pair);
        DefaultTreeModel model = (DefaultTreeModel)commandsTree.getModel();
        model.reload(getSelectedTreeNode());
        updateListBox(item);
    }//GEN-LAST:event_removeActionPerformed

    /**
     * Reset *All* menu items to their default Key Bindings
     * @param evt the event
     */
    /**
     * Open dialog to add a key binding to the selected menu item
     * @param evt the event
     */
    private void addActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addActionPerformed
        EMenuItem item = getSelectedMenuItem();
        if (item == null || item == EMenuItem.SEPARATOR || item instanceof EMenu) return;
        EditKeyBinding dialog = new EditKeyBinding(item, menuBar);
		dialog.setVisible(true);

        // update tree view
        DefaultTreeModel model = (DefaultTreeModel)commandsTree.getModel();
        model.reload(getSelectedTreeNode());
        updateListBox(item);
    }//GEN-LAST:event_addActionPerformed

    /** Exit the Application */
    private void exitForm(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_exitForm

    }//GEN-LAST:event_exitForm

    // -------------------------------- Tree View Population -------------------------------

    /** Build tree of menu commands */
    private void buildCommandsTree() {
        DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();

        // convert menuBar to tree
        for (EMenuItem menu: menuBar.getItems()) {
            DefaultMutableTreeNode menuNode = new DefaultMutableTreeNode(new KeyBoundTreeNode(menu));
            rootNode.add(menuNode);
            addMenu(menuNode, (EMenu)menu);
        }
        EMenu hiddenMenu = menuBar.getHiddenMenu();
        if(hiddenMenu != null) {
            DefaultMutableTreeNode menuNode = new DefaultMutableTreeNode(new KeyBoundTreeNode(hiddenMenu));
            rootNode.add(menuNode);
            addMenu(menuNode, hiddenMenu);
        }

        commandsTree.setModel(new DefaultTreeModel(rootNode));
        // single selection as default
		commandsTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);

		// do not show top-level
		commandsTree.setRootVisible(false);
		commandsTree.setShowsRootHandles(true);
		commandsTree.setToggleClickCount(3);
        commandsTree.addTreeSelectionListener(this);
    }

    /** Adds menu items to parentNode, which represents Menu menu. */
    private void addMenu(DefaultMutableTreeNode parentNode, EMenu menu) {
        for (EMenuItem menuItem: menu.getItems()) {
            DefaultMutableTreeNode menuItemNode = new DefaultMutableTreeNode(new KeyBoundTreeNode(menuItem));
            parentNode.add(menuItemNode);
            if (menuItem instanceof EMenu)
                addMenu(menuItemNode, (EMenu)menuItem);              // recurse
        }
    }

    // ---------------------------- Tree Selection Listener -----------------------

    /**
     * Called when selection of Node in tree changes.
     * It updates the list box to reflect the current tree selection.
     */
    public void valueChanged(javax.swing.event.TreeSelectionEvent e) {
        TreePath path = e.getPath();
        if (path == null) return;
        Object obj = path.getLastPathComponent();
        DefaultMutableTreeNode node = (DefaultMutableTreeNode)obj;
        Object n = node.getUserObject();
        if (!(n instanceof KeyBoundTreeNode)) return;
        KeyBoundTreeNode treeNode = (KeyBoundTreeNode)n;
        updateListBox(treeNode.getMenuItem());
    }

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

    /**
     * Update list box with item's key bindings
     * @param item display key bindings for this item
     */
    private void updateListBox(EMenuItem item) {
        if (item == null) {
            bindingsJList.setListData(new Object [] {});
            return;
        }
        KeyBindings bindings = menuBar.getKeyBindings(item);
        if (bindings == null) {
            bindingsJList.setListData(new Object [] {});
            return;
        }
        ArrayList<KeyStrokePair> list = new ArrayList<KeyStrokePair>();
        for (Iterator<KeyStrokePair> it = bindings.getKeyStrokePairs(); it.hasNext(); ) {
            KeyStrokePair pair = it.next();
            list.add(pair);
        }
        bindingsJList.setListData(list.toArray());
    }

    private KeyStrokePair getListBoxSelected() {
        Object value = bindingsJList.getSelectedValue();
        if (value == null) return null;
        return (KeyStrokePair)value;
    }

    //-------------------------- Private Utility Methods ----------------------------

    /**
     * Get selected menu item in tree view
     * @return the selected menu item, or null if none.
     */
    private EMenuItem getSelectedMenuItem() {
        DefaultMutableTreeNode node = getSelectedTreeNode();
        if (node == null) return null;
        Object obj = node.getUserObject();
        if (!(obj instanceof KeyBoundTreeNode)) return null;
        KeyBoundTreeNode treeNode = (KeyBoundTreeNode)obj;
        EMenuItem item = treeNode.getMenuItem();
        return item;
    }

    /** get selected DefaultMutableTreeNode.
     * Returns null if no valid DefaultMutableTree node selected in tree.
     */
    private DefaultMutableTreeNode getSelectedTreeNode() {
        TreePath path = commandsTree.getSelectionPath();
        if (path == null) return null;
        DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
        return node;
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton add;
    private javax.swing.JList bindingsJList;
    private javax.swing.JTree commandsTree;
    private javax.swing.JPanel editKeyBindingsPanel;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JPanel jPanel4;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JButton remove;
    private javax.swing.JButton resetitem;
    // End of variables declaration//GEN-END:variables

}
