/*
 * Display_abbreviations.bsh - a BeanShell macro script for the
 * jEdit text editor - displays all defined abbreviations
 * Copyright (C) 2001 John Gellene
 * email: jgellene@nyc.rr.com
 * http://community.jedit.org
 *
 * This program 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 2
 * of the License, or any later version.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: Display_Abbreviations.bsh 5224 2005-06-08 22:33:27Z orutherfurd $
 *
 * requires JDK 1.2, jEdit3.0
 *
 * Notes on use:
 *
 * This macro will display a sorted list of all defined abbreviations
 * in a dialog.  A combo box lists all eidting modes for which abbreviations
 * are currently defined, as well as the "global" abbreviation set.
 *
 * Pressing a letter key will cause the table to scroll to the first row
 * with a label beginning with the letter (or the imeediately preceding row if
 * no item begins with that letter).  The table is read-only; the dialog is
 * dismissed by clicking "OK" or pressing Esc or Enter.
 *
 * The macro has two global constants defined to permit customization of the
 * script's behavior.  STARTING_SET contains the name of the abbreviation set
 * that will be first displayed.  If the other variable, EXCLUDE_EMPTY_SETS,
 * is set to true, the drop-down menu and the results of clicking the
 * "Write All" button will only include abbreviations sets containing
 * entries.
 *
 * The table being displayed at any time is not updated if changes are made to
 * the abbreviation set in the "Global Options" dialog.  If EXCLUDE_EMPTY_SETS
 * is set to true, the drop-down list will not by updated.  Clsoing the dialog
 * and running the macro again will obtain current data.
 *
 *
 * Checked for jEdit 4.0 API
 *
 */

import javax.swing.table.*;

/*
 * getActiveSets()
 * This returns an array with the names of the abbreviation sets
 * (beginning with "global").  If EXCLUDE_EMPTY_SETS is set to true, only sets
 * with abbreviations are included.
 */
Object[] getActiveSets()
{
	Mode[] modes = jEdit.getModes();
	Vector setVector = new Vector(modes.length + 1);
	setVector.addElement("global");
	for(int i = 0; i < modes.length; i++)
	{
		String name = modes[i].getName();
		if(EXCLUDE_EMPTY_SETS)
		{
			Hashtable ht = Abbrevs.getModeAbbrevs().get(name);
			if( ht == null || ht.isEmpty())
				continue;
		}
		setVector.addElement(name);
	}
	Object[] sets = new Object[setVector.size()];
	setVector.copyInto(sets);
	return sets;
}

/*
 * makeTableDataForMode()
 * This extracts the abbreviations from the named set.  If extraction is
 * successful, the vector named by the first parameter will have its
 * elements removed before the variable is set to the newly created vector.
 */
Vector makeTableDataForMode(Vector v, String name)
{
	if(name.equals(currentSet))
		return v;
	Hashtable htable = null;
	if(name.equals("global"))
	{
		htable = Abbrevs.getGlobalAbbrevs();
	}
	else
	{
		Hashtable modeAbbrevs = Abbrevs.getModeAbbrevs();
		htable = modeAbbrevs.get(name);
	}
	if(htable != null)
	{
		Enumeration abbrevEnum = htable.keys();
		Enumeration expandEnum = htable.elements();
		Vector newData = new Vector(20, 5);
		while(abbrevEnum.hasMoreElements())
		{
			Vector row = new Vector(2);
			row.addElement(abbrevEnum.nextElement());
			row.addElement(expandEnum.nextElement());
			newData.addElement(row);
		}
		MiscUtilities.quicksort(newData,
			new MiscUtilities.StringICaseCompare());
		currentSet = name;
		if( v != null)
			v.removeAllElements();
		v = newData;
	}
	return v;
}

/*
 * showAbbrevs()
 * This is the macro's principal method.
 */
void showAbbrevs()
{
	currentSet = null;
	data = makeTableDataForMode(data, STARTING_SET);
	if(data.size() == 0)
	{
		STARTING_SET = "global";
		data = makeTableDataForMode(data, STARTING_SET);
	}
	Vector columnNames = new Vector(2);
	columnNames.addElement(new String("Abbreviation"));
	columnNames.addElement(new String("Expansion"));
	table = new JTable();
	table.setModel(new DefaultTableModel(data, columnNames));
	table.setRowSelectionAllowed(true);
	/* 	The next line prevents the table from being edited.
	 * 	The normal approach in Java would be to subclass the TableModel
	 * 	associated with the JTable and define TableModel.isCellEditable()
	 * 	to return false.  However, BeanShell does not allow conventional
	 *  class creation, and the desired behavior cannot be achieved using
	 *  its scripted object feature.
	 */
	table.setDefaultEditor(Object.class, null);
	if(table.getRowCount() != 0)
		table.setRowSelectionInterval(0,0);
	tablePane = new JScrollPane(table);
	tablePane.setPreferredSize(new Dimension(450, 300));

	combo = new JComboBox(abbrevSets);
	Dimension dim = combo.getPreferredSize();
	dim.width = Math.max(dim.width, 120);
	combo.setPreferredSize(dim);
	combo.setSelectedItem(STARTING_SET);
	comboPanel = new JPanel(new FlowLayout());
	comboPanel.add(new JLabel("Abbreviation set:"));
	comboPanel.add(combo);

	close = new JButton("Close");
	write_set = new JButton("Write set");
	write_all = new JButton("Write all");
	buttonPanel = new JPanel(new FlowLayout());
	buttonPanel.add(write_set);
	buttonPanel.add(write_all);
	buttonPanel.add(close);

	close.addActionListener(this);
	write_set.addActionListener(this);
	write_all.addActionListener(this);
	combo.addActionListener(this);
	void actionPerformed(e)
	{
		Component source = e.getSource();
		if(source == close)
			dialog.hide();
		else if(source == write_set)
			writeTableToNewBuffer(super.data, (String)combo.getSelectedItem());
		else if(source == write_all)
			writeAllToNewBuffer();
		else if(source == combo)
		{
			super.data = makeTableDataForMode(super.data, (String)combo.getSelectedItem());
			if( data != null)
			{
				DefaultTableModel model = (DefaultTableModel)table.getModel();
				model.setDataVector(super.data, columnNames);
			}
		}
	}

	// workaround required by Swing bug; scheduled to be fixed in JDK 1.4
	combo.getComponent(0).addKeyListener(this);
	table.addKeyListener(this);
	write_set.addKeyListener(this);
	write_all.addKeyListener(this);
	close.addKeyListener(this);
	void keyPressed(e)
	{
		if(combo.isPopupVisible()) return;
		if(e.getKeyCode() == KeyEvent.VK_ESCAPE ||
			e.getKeyCode() == KeyEvent.VK_ENTER)
		{
			dialog.hide();
		}
		else if(e.getSource() != combo)
		{
			char ch = e.getKeyChar();
			if(Character.isLetter(ch))
			{
				e.consume();
				row = findFirstItem(ch);
				/* The next few lines set the last visible row
				 * of the table so that you can look ahead of
				 * the selected row.
				 */
				visibleRows =
					table.getVisibleRect().height / table.getRowHeight();
				oldRow = table.getSelectedRow();
				table.setRowSelectionInterval(row,row);
				if (visibleRows > 5 && row - oldRow > visibleRows - 3)
				{
					row = Math.min( super.data.size() - 1, row + 3);
				}
				table.scrollRectToVisible(table.getCellRect(row,0,true));
			}
		}
	}
	/*
	 * Having these members of KeyListener implemented as no-ops
	 * will speedup execution.  Otherwise BeanShell throws an
	 * exception that jEdit handles internally.
	 * Under BeanShell 1.2, implementation of these methods is
	 * required.
	 */
	void keyReleased(e) {}
	void keyTyped(e) {}

	/*
	 * findFirstItem()
	 * A simple linear search for the table entry that begins with the
	 * given letter.  It returns the first row with an entry beginning with
	 * the letter, or the immdediately preceding row if there is no match
	 * on the letter.
	 *
	 */
	int findFirstItem(char ch)
	{
		ch = Character.toUpperCase(ch);
		int row = 0;
		for(int i = 0; i < data.size(); ++i)
		{
			String name = ((Vector)data.elementAt(i)).elementAt(0);
			char ch_test = Character.toUpperCase(name.charAt(0));
			if( ch_test > ch) break;
			else
			{
				row = i;
				if( ch_test == ch) break;
			}
		}
		return row;
	}

	title = "Abbreviation list";
	dialog = new JDialog(view, title, false);
	c = dialog.getContentPane();
	c.add(tablePane, "Center");
	c.add(comboPanel, "North");
	c.add(buttonPanel, "South");
	dialog.getRootPane().setDefaultButton(close);
	dialog.pack();
	dialog.setLocationRelativeTo(view);
	dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
	dialog.show();
}

/*
 * The next four methods deal with writing the abbreviation sets to a new
 * text buffer.
 */
void writeTableToNewBuffer(Vector v, String setName)
{
	Log.log(Log.DEBUG, BeanShell.class,
		"calling writeTableToNewBuffer for " + setName);
	writeHeader();
	Log.log(Log.DEBUG, BeanShell.class, "size of vector = "
	 + String.valueOf(v.size()));
	writeSet(v, setName);
}

// saved the currentSet and deleted it for makeTableDataForMode()-invocation
// because otherwise the currently in the Dialog displayed set has no abbrevs
// in the output written to the buffer.
void writeAllToNewBuffer()
{
	writeHeader();
	Vector vOut = new Vector();
	savedCurrentSet = currentSet;
	for( int i = 0; i < abbrevSets.length; ++i)
	{
		currentSet = null;
		String setName = (String)abbrevSets[i];
		vOut = makeTableDataForMode(vOut, setName);
		writeSet(vOut, setName);
		textArea.setSelectedText("\n\n");
	}
	currentSet = savedCurrentSet;
}

void writeHeader()
{
	jEdit.newFile(view);
	textArea.setSelectedText("jEdit Abbreviation Table\n\n");
}

/*
 * This truncates the definition text at 17 characters and the expansion text
 * at 58.
 */
void writeSet(Vector v, String setName)
{
	textArea.setSelectedText("Abbreviation set: " + setName + "\n");
	textArea.setSelectedText("Abbreviation    Expansion\n\n");
	if(v.size() == 0)
		textArea.setSelectedText("<< No abbreviations >>\n");
	else for( int i = 0; i < v.size(); ++i)
	{
		StringBuffer sb = new StringBuffer(85);
		spaceString = "                    ";
		char[] space = spaceString.toCharArray();
		Vector row = (Vector)v.elementAt(i);
		abbrevName = (String)row.elementAt(0);
		if(abbrevName == null) continue;
		if(abbrevName.length() > 17)
			abbrevName = abbrevName.substring(0, 14) + "...";
		sb.append(abbrevName);
		sb.append(space, 0, 18 - (abbrevName.length()));
		expansion = row.elementAt(1);
		if(shortcut1 != null)
		{
			if(expansion.length() > 58)
				expansion = expansion.substring(0, 55) + "...";
			sb.append(expansion);
		}
		sb.append('\n');
		textArea.setSelectedText(sb.toString());
	}
}


/*
 * main routine, including definition of global variables
 */
STARTING_SET = "global";
EXCLUDE_EMPTY_SETS = true;
abbrevSets = getActiveSets();
currentSet = null;
Vector data = new Vector(20, 5);
showAbbrevs();

/*
	Macro index data (in DocBook format)

<listitem>
    <para><filename>Display_Abbreviations.bsh</filename></para>
    <abstract><para>
        Displays the abbreviations registered for each of jEdit's
        editing modes.
    </para></abstract>
    <para>
        The macro provides a read-only view of the abbreviations
        contained in the <quote>Abbreviations</quote> option pane.  Pressing
        a letter key will scroll the table to the first entry beginning with
        that letter.  A further option is provided to write a selected mode's
        abbreviations or all abbreviations in a text buffer for printing as a
        reference. Notes in the source code listing point out some display options
        that are configured by modifying global variables.
    </para>
</listitem>

*/

// end Display_Abbreviations.bsh

