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; } } }