/**
*
*/
package org.korsakow.ide.ui.components;
import java.applet.Applet;
import java.awt.Component;
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EventObject;
import javax.swing.CellEditor;
import javax.swing.DefaultListModel;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;
/**
* Supports:
* -editing
* -ToolTipRenderer
* -rollover states
* @author d
*
*/
public class KList extends JList implements CellEditorListener
{
public static final String ROLLOVER_PROPERTY = "KList.ROLLOVER_PROPERTY";
/** If editing, the <code>Component</code> that is handling the editing. */
transient protected Component editorComp;
/**
* The active cell editor object, that overwrites the screen real estate
* occupied by the current cell and allows the user to change its contents.
* {@code null} if the table isn't currently editing.
*/
transient protected ListCellEditor cellEditor;
/** Identifies the row of the cell being edited. */
transient protected int editingRow;
private CellEditorRemover editorRemover;
private ToolTipRenderer tooltipRenderer = null;
private MouseInputListener rolloverListener;
private boolean editable = true;
public KList()
{
cellEditor = new DefaultListCellEditor(new JTextField());
}
public void setToolTipRenderer(ToolTipRenderer renderer)
{
this.tooltipRenderer = renderer;
}
public ToolTipRenderer getToolTipRenderer()
{
return tooltipRenderer;
}
public String getToolTipText(MouseEvent event)
{
if (tooltipRenderer != null)
return tooltipRenderer.getToolTipText(event);
else
return super.getToolTipText(event);
}
public ListCellEditor getCellEditor()
{
return cellEditor;
}
public void setRolloverEnabled(boolean b)
{
if (b) {
if (rolloverListener == null) {
rolloverListener = new RolloverListener(this);
addMouseListener(rolloverListener);
addMouseMotionListener(rolloverListener);
}
} else {
if (rolloverListener != null) {
removeMouseListener(rolloverListener);
removeMouseMotionListener(rolloverListener);
}
rolloverListener = null;
}
}
/**
* Returns the index maintained by the rollover mechanism. Some UI implementations (such as BasicListUI) will
* recurse if you attempt to get the cell bounds from within a CellRenderer call.
*
* This method circumvents that limitation.
*
* @return -1 if setRolloverEnabled(true) has not been called
*/
public int getRolloverIndex()
{
Integer index = (Integer)getClientProperty(ROLLOVER_PROPERTY);
return index!=null?index:-1;
}
/**
* Sets the active cell editor.
*
* @param anEditor the active cell editor
* @see #cellEditor
* @beaninfo
* bound: true
* description: The table's active cell editor.
*/
public void setCellEditor(ListCellEditor anEditor) {
ListCellEditor oldEditor = cellEditor;
cellEditor = anEditor;
firePropertyChange("listCellEditor", oldEditor, anEditor);
} public boolean isEditable()
{
return editable;
}
protected void setEditingRow(int row)
{
editingRow = row;
}
/**
* Returns true if a cell is being edited.
*
* @return true if the table is editing a cell
* @see #editingColumn
* @see #editingRow
*/
public boolean isEditing() {
return (cellEditor == null)? false : true;
}
/**
* Discards the editor object and frees the real estate it used for
* cell rendering.
*/
public void removeEditor() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().
removePropertyChangeListener("permanentFocusOwner", editorRemover);
editorRemover = null;
CellEditor editor = getCellEditor();
if(editor != null) {
editor.removeCellEditorListener(this);
if (editorComp != null) {
remove(editorComp);
}
Rectangle cellRect = getCellBounds(editingRow, editingRow);
setCellEditor(null);
setEditingRow(-1);
editorComp = null;
repaint(cellRect);
}
}
public boolean editCellAt(int row, EventObject e){
if (cellEditor != null && !cellEditor.stopCellEditing()) {
return false;
}
if (row < 0 || row >= getModel().getSize()) {
return false;
}
if (!isEditable())
return false;
if (editorRemover == null) {
KeyboardFocusManager fm =
KeyboardFocusManager.getCurrentKeyboardFocusManager();
editorRemover = new CellEditorRemover(fm);
fm.addPropertyChangeListener("permanentFocusOwner", editorRemover);
}
ListCellEditor editor = getCellEditor();
if (editor != null && editor.isCellEditable(e)) {
editorComp = prepareEditor(editor, row);
if (editorComp == null) {
removeEditor();
return false;
}
editorComp.setBounds(getCellBounds(row, row));
add(editorComp);
editorComp.validate();
setCellEditor(editor);
setEditingRow(row);
editor.addCellEditorListener(this);
return true;
}
return false;
}
/**
* Prepares the editor by querying the data model for the value and
* selection state of the cell at <code>row</code>, <code>column</code>.
* <p>
* <b>Note:</b>
* Throughout the table package, the internal implementations always
* use this method to prepare editors so that this default behavior
* can be safely overridden by a subclass.
*
* @param editor the <code>TableCellEditor</code> to set up
* @param row the row of the cell to edit,
* where 0 is the first row
* @param column the column of the cell to edit,
* where 0 is the first column
* @return the <code>Component</code> being edited
*/
public Component prepareEditor(ListCellEditor editor, int row) {
Object value = getModel().getElementAt(row);
boolean isSelected = isSelectedIndex(row);
Component comp = editor.getListCellEditorComponent(this, value, isSelected,
row);
if (comp instanceof JComponent) {
JComponent jComp = (JComponent)comp;
if (jComp.getNextFocusableComponent() == null) {
jComp.setNextFocusableComponent(this);
}
}
return comp;
}
protected class CellEditorRemover implements PropertyChangeListener {
KeyboardFocusManager focusManager;
public CellEditorRemover(KeyboardFocusManager fm) {
this.focusManager = fm;
}
public void propertyChange(PropertyChangeEvent ev) {
if (!isEditing() || getClientProperty("terminateEditOnFocusLost") != Boolean.TRUE) {
return;
}
Component c = focusManager.getPermanentFocusOwner();
while (c != null) {
if (c == KList.this) {
// focus remains inside the table
return;
} else if ((c instanceof Window) ||
(c instanceof Applet && c.getParent() == null)) {
if (c == SwingUtilities.getRoot(KList.this)) {
if (!getCellEditor().stopCellEditing()) {
getCellEditor().cancelCellEditing();
}
}
break;
}
c = c.getParent();
}
}
}
/**
* Invoked when editing is finished. The changes are saved and the
* editor is discarded.
* <p>
* Application code will not use these methods explicitly, they
* are used internally by JTable.
*
* @param e the event received
* @see CellEditorListener
*/
public void editingStopped(ChangeEvent e) {
// Take in the new value
ListCellEditor editor = getCellEditor();
if (editor != null) {
Object value = editor.getCellEditorValue();
if (getModel() instanceof DefaultListModel)
((DefaultListModel)getModel()).setElementAt(value, editingRow);
else
if (getModel() instanceof MutableListModel)
((MutableListModel)getModel()).setElementAt(value, editingRow);
removeEditor();
}
}
/**
* Invoked when editing is canceled. The editor object is discarded
* and the cell is rendered once again.
* <p>
* Application code will not use these methods explicitly, they
* are used internally by JTable.
*
* @param e the event received
* @see CellEditorListener
*/
public void editingCanceled(ChangeEvent e) {
removeEditor();
}
protected static class RolloverListener extends MouseInputAdapter
{
private KList list;
public RolloverListener(KList list)
{
this.list = list;
}
private void update(MouseEvent e) {
Integer oldIndex = (Integer)list.getClientProperty(ROLLOVER_PROPERTY);
Integer index = null;
if (e != null) {
index = list.locationToIndex(e.getPoint());
list.putClientProperty(ROLLOVER_PROPERTY, index);
list.repaint(list.getCellBounds(index, index));
} else {
list.putClientProperty(ROLLOVER_PROPERTY, -1);
}
if (oldIndex!=null && oldIndex!=index) {
Rectangle rect = list.getCellBounds(oldIndex, oldIndex);
if (rect != null)
list.repaint(rect);
}
}
public void mouseExited(MouseEvent e) {
update(null);
}
public void mouseDragged(MouseEvent e) {
update(e);
}
public void mouseMoved(MouseEvent e) {
update(e);
}
}
}