package com.onaro.util.jfc.tables.editors;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EventObject;
import javax.swing.AbstractCellEditor;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellEditor;
import org.apache.commons.lang3.StringUtils;
import com.onaro.util.jfc.HtmlDocBuilder;
/**
* An editor that enables users to copy only a portion of the text rendered in the cell. If possible, it'll be
* using the text that the cell's renderer would be used. Otherwise it'll display the "toString()" of the cell's value.
*/
public class CutPasteCellEditor extends AbstractCellEditor implements TableCellEditor {
private static final long serialVersionUID = 1L;
/**
* The actual editor is a text pane, although it's capable painting an icon the same way a {@link JLabel} does.
*/
protected IconTextPane iconTextPane = new IconTextPane();
/**
* An integer specifying the number of clicks needed to start editing. Even if clickCountToStart
is
* defined as zero, it will not initiate until a click occurs.
*/
private final static int CLICK_COUNT_TO_START = 2;
/**
* Used to persist the value sent in to edit
*/
private Object value = null;
public Object getCellEditorValue() {
return value;
}
/**
* Implements the TableCellEditor
interface.
*/
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
iconTextPane.reset();
this.value = value;
Component renderer = table.prepareRenderer(table.getCellRenderer(row, column), row, column);
if (renderer instanceof JLabel) {
iconTextPane.setValue((JLabel) renderer);
} else {
iconTextPane.setValue(value);
}
return iconTextPane;
}
/**
* Returns true if anEvent
is not a MouseEvent
. Otherwise, it returns true if
* the necessary number of clicks have occurred, and returns false otherwise.
*
* @param anEvent
* the event
* @return true if cell is ready for editing, false otherwise
* @see #shouldSelectCell
*/
public boolean isCellEditable(EventObject anEvent) {
if (anEvent instanceof MouseEvent) {
return ((MouseEvent) anEvent).getClickCount() >= CLICK_COUNT_TO_START;
}
return true;
}
/**
* A text-pane is used to do the actual editing of the cell. It is initialized by either getting the text and icon
* of a JLabel or by using the "toString()" of the cell-value.
*/
private class IconTextPane extends JTextPane {
private static final long serialVersionUID = 1L;
/**
* The icon to be painted if was set in the {@link JLabel} from which it was initialized.
*/
private Icon icon;
/**
* Horizontal offset when painting the text - used when an icon is present.
*/
private int textOffset;
/**
* Used to fix inconsistency in the alignment of html and plain text.
*/
private boolean html;
/**
* Initialize the text-area, setting up the listeners needed for it to be a cell-editor.
*/
public IconTextPane() {
setContentType("text/html"); //$NON-NLS-1$
setEditable(false);
setBorder(null);
addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
stopCellEditing();
}
});
addFocusListener(new FocusListener() {
public void focusGained(FocusEvent e) {
}
public void focusLost(FocusEvent e) {
cancelCellEditing();
}
});
}
/**
* Invoke before every use to cleanup temporary variables.
*/
public void reset() {
textOffset = 0;
icon = null;
html = false;
}
/**
* Initialize the text and icon from the {@link JLabel} which is the renderer of the cell being edited.
*
* @param label
* the renderer of the cell being edited
*/
public void setValue(JLabel label) {
icon = label.getIcon();
if (icon != null) {
textOffset = icon.getIconWidth() + label.getIconTextGap();
}
setValue(label.getText());
}
/**
* Initialize the text to be edited from the "toString" of the given value. Also determines if the text is plain
* or HTML to augment the alignment when painting.
*
* @param value
* the text to be edited
*/
public void setValue(Object value) {
String valueStr = value != null ? value.toString().trim() : StringUtils.EMPTY;
String prefix = valueStr.substring(0, Math.min(valueStr.length(), 6));
html = "".equalsIgnoreCase(prefix); //$NON-NLS-1$
/* Leading slash causes the HTML document builder to interpret the text as some kind of path which is ignored. */
if(valueStr.length() > 0 && valueStr.charAt(0) == '/') {
// Replace the slash with the HTML equivalent
valueStr = "/" + valueStr.substring(1); //$NON-NLS-1$
}
iconTextPane.setDocument(HtmlDocBuilder.buildHTMLDocument(null, valueStr));
SwingUtilities.invokeLater(new Runnable() {
public void run() {
iconTextPane.selectAll();
}
});
}
public void paint(Graphics g) {
if (!html) {
g.translate(1, 1);
}
if (textOffset > 0) {
g.setColor(getBackground());
g.fillRect(0, 0, textOffset, getHeight());
g.translate(textOffset, 0);
}
super.paint(g);
if (textOffset > 0) {
g.translate(-textOffset, 0);
}
if (icon != null) icon.paintIcon(iconTextPane, g, 0, 0);
}
/**
* Override getSelectedText() to strip an unnecessary new-line character
* that's produced by the HTML document. This avoids including the new-line
* being included in the contents that are copied from the editor.
*/
@Override
public String getSelectedText() {
String selectedText = super.getSelectedText();
if (selectedText != null && selectedText.startsWith("\n")) { //$NON-NLS-1$
selectedText = selectedText.substring(1);
}
return selectedText;
}
}
}