/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.explorer.view; import java.beans.*; import java.util.EventObject; import java.text.MessageFormat; import java.lang.reflect.InvocationTargetException; import java.awt.Graphics; import java.awt.Component; import java.lang.ref.WeakReference; import java.util.Map; import java.util.WeakHashMap; import javax.swing.*; import javax.swing.table.*; import javax.swing.border.*; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.accessibility.AccessibleContext; import javax.accessibility.AccessibleRole; import javax.swing.tree.TreeNode; import org.openide.ErrorManager; import org.openide.util.Mutex; import org.openide.util.NbBundle; import org.openide.nodes.Node; import org.openide.nodes.Node.Property; import org.openide.explorer.propertysheet.*; /** * TableCellEditor/Renderer implementation. Component returned is the PropertyPanel * * @author Jan Rojcek */ class TableSheetCell extends AbstractCellEditor implements TableModelListener, PropertyChangeListener, TableCellEditor, TableCellRenderer { /* Table sheet cell works only with NodeTableModel */ private NodeTableModel tableModel; /* Determines how to paint renderer */ private Boolean flat; public TableSheetCell(NodeTableModel tableModel) { this.tableModel = tableModel; setFlat(false); } /** * Set how to paint renderer. * @param f <code>true</code> means flat, <code>false</code> means with button border */ public void setFlat(boolean f) { nullPanelBorder = f ? (Border)new EmptyBorder(1, 1, 1, 1) : (Border)new CompoundBorder( new MatteBorder(0, 0, 1, 1, UIManager.getColor("controlDkShadow")), new MatteBorder(1, 1, 0, 0, UIManager.getColor("controlLtHighlight")) ); nullPanelFocusBorder = new CompoundBorder( new CompoundBorder(nullPanelBorder, new EmptyBorder(1, 1, 1, 1)), new MatteBorder(1, 1, 1, 1, UIManager.getColor("Button.focus")) // NOI18N ); flat = f ? Boolean.TRUE : Boolean.FALSE; } // // Editor // /** Property model used in cell editor property panel */ private WrapperPropertyModel propModel; /** Actually edited node (its property) */ private Node node; /** Edited property */ private Property prop; /** Returns <code>null<code>. * @return <code>null</code> */ public Object getCellEditorValue() { return null; } /** Returns editor of property. * @param table * @param value * @param isSelected * @param r row * @param c column * @return <code>PropertyPanel</code> */ public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int r, int c) { prop = (Property)value; node = tableModel.nodeForRow(r); propModel = new WrapperPropertyModel(node, prop); node.addPropertyChangeListener(this); tableModel.addTableModelListener(this); // create property panel PropertyPanel propPanel = new FocusHackedPropertyPanel(propModel, prop.canWrite() ? 0 //PropertyPanel.PREF_INPUT_STATE : PropertyPanel.PREF_READ_ONLY); propPanel.putClientProperty("flat", flat); // NOI18N propPanel.setBackground(table.getSelectionBackground()); return propPanel; } /** Cell should not be selected * @param ev event * @return <code>false</code> */ public boolean shouldSelectCell(EventObject ev) { return true; } /** Return true. * @param e event * @return <code>true</code> */ public boolean isCellEditable(EventObject e) { return true; } /** Forwards node property change to property model * @param evt event */ public void propertyChange(PropertyChangeEvent evt) { Mutex.EVENT.readAccess(new Runnable() { public void run() { if (propModel != null) { propModel.firePropertyChange(); } } }); } /** * Detaches listeners. * Calls <code>fireEditingStopped</code> and returns true. * @return true */ public boolean stopCellEditing() { if (prop != null) detachEditor(); return super.stopCellEditing(); } /** * Detaches listeners. * Calls <code>fireEditingCanceled</code>. */ public void cancelCellEditing() { if (prop != null) detachEditor(); super.cancelCellEditing(); } /** Table has changed. If underlied property was switched then cancel editing. * @param e event */ public void tableChanged(TableModelEvent e) { cancelCellEditing(); } /** Removes listeners and frees resources. */ private void detachEditor() { node.removePropertyChangeListener(this); tableModel.removeTableModelListener(this); node = null; prop = null; propModel = null; } // // Renderer // /** Default header renderer */ private TableCellRenderer headerRenderer = (new JTableHeader()).getDefaultRenderer(); /** Null panel is used if cell value is null */ private NullPanel nullPanel; private Border nullPanelBorder; private Border nullPanelFocusBorder; /** Two-tier cache for property panels * Map<TreeNode, WeakHashMap<Node.Property, Reference<FocusedPropertyPanel>> */ private Map panelCache = new WeakHashMap(); // weak! #31275 /** Getter for actual cell renderer. * @param table * @param value * @param isSelected * @param hasFocus * @param row * @param column * @return <code>PropertyPanel</code> */ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { // Header renderer if (row == -1) { Component comp = headerRenderer.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column); if (comp instanceof JComponent) { String tip = column > 0 ? tableModel.propertyForColumn(column).getShortDescription() : table.getColumnName( 0 ); ((JComponent) comp).setToolTipText(tip); } return comp; } Property prop = (Property)value; Node node = tableModel.nodeForRow(row); if (prop != null) { FocusedPropertyPanel propPanel = obtainPanel(node, prop); propPanel.setFocused(hasFocus); propPanel.setToolTipText(prop.getShortDescription()); propPanel.setOpaque(true); if (isSelected) propPanel.setBackground(table.getSelectionBackground()); else propPanel.setBackground(table.getBackground()); return propPanel; } if (nullPanel == null) { nullPanel = new NullPanel(node); nullPanel.setOpaque(true); } else { nullPanel.setNode(node); } if (isSelected) nullPanel.setBackground(table.getSelectionBackground()); else nullPanel.setBackground(table.getBackground()); if (hasFocus) { nullPanel.setBorder(nullPanelFocusBorder); } else { nullPanel.setBorder(nullPanelBorder); } return nullPanel; } private FocusedPropertyPanel obtainPanel (Node node, Property prop) { FocusedPropertyPanel propPanel = null; TreeNode visualizer = Visualizer.findVisualizer(node); Map innerCache = (Map)panelCache.get(visualizer); if (innerCache == null) { // outer cache miss innerCache = new WeakHashMap(); propPanel = createPropPanel(node, prop); propPanel.putClientProperty("flat", flat); // NOI18N innerCache.put(prop, propPanel); panelCache.put(visualizer, innerCache); } else { propPanel = (FocusedPropertyPanel)innerCache.get(prop); if (propPanel == null) { // inner cache miss propPanel = createPropPanel(node, prop); propPanel.putClientProperty("flat", flat); // NOI18N innerCache.put(prop, propPanel); } else { // cache hit ((WrapperPropertyModel)propPanel.getModel()).firePropertyChange(); } } return propPanel; } private static FocusedPropertyPanel createPropPanel (Node node, Property prop) { WrapperPropertyModel propModel = new WrapperPropertyModel(node, prop); // Now it doesn't differentiate between read-only and read-write property FocusedPropertyPanel propPanel = new FocusedPropertyPanel(propModel, 0); //prop.canWrite() ? 0 : PropertyPanel.PREF_READ_ONLY); return propPanel; } /** Wraps Node.Property with PropertyModel. */ private static class WrapperPropertyModel implements ExPropertyModel { PropertyChangeSupport support = new PropertyChangeSupport(this); /** Wrapped property */ Property prop; /** Node the property belongs to */ Node node; /** Creates new wrapped property * @param prop wrapped property */ public WrapperPropertyModel(Node node, Property prop) { this.prop = prop; this.node = node; } /** Getter for current value of a property. */ public Object getValue() throws InvocationTargetException { try { return prop.getValue(); } catch (IllegalAccessException e) { ErrorManager.getDefault ().notify(ErrorManager.INFORMATIONAL, e); throw new InvocationTargetException(e); } } /** Setter for a value of a property. * @param v the value * @exeception InvocationTargetException */ public void setValue(Object v) throws InvocationTargetException { if (!prop.canWrite()) return; try { prop.setValue(v); } catch (IllegalAccessException e) { ErrorManager.getDefault ().notify(ErrorManager.INFORMATIONAL, e); throw new InvocationTargetException(e); } } /** The class of the property. */ public Class getPropertyType() { return prop.getValueType(); } /** The class of the property editor or <CODE>null</CODE> * if default property editor should be used. */ public Class getPropertyEditorClass() { return prop.getPropertyEditor().getClass(); } /** Add listener to change of the value. */ public void addPropertyChangeListener(PropertyChangeListener l) { support.addPropertyChangeListener(l); } /** Remove listener to change of the value. */ public void removePropertyChangeListener(PropertyChangeListener l) { support.removePropertyChangeListener(l); } public void firePropertyChange() { support.firePropertyChange(PROP_VALUE, null, null); } /** * Returns an array of beans/nodes that this property belongs * to. */ public Object[] getBeans() { return new Object[] {node}; } /** * Returns descriptor describing the property. */ public FeatureDescriptor getFeatureDescriptor() { return prop; } } private static class NullPanel extends JPanel { private WeakReference weakNode; NullPanel(Node node) { this.weakNode = new WeakReference(node); } void setNode(Node node) { this.weakNode = new WeakReference(node); } public AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessibleNullPanel(); } return accessibleContext; } private class AccessibleNullPanel extends AccessibleJPanel { AccessibleNullPanel() {} public String getAccessibleName() { String name = super.getAccessibleName(); if (name == null) { name = getString("ACS_NullPanel"); } return name; } public String getAccessibleDescription() { String description = super.getAccessibleDescription(); if (description == null) { Node node = (Node)weakNode.get(); if (node != null) { description = MessageFormat.format( getString("ACSD_NullPanel"), new Object[] { node.getDisplayName() } ); } } return description; } } } /** Table cell renderer component. Paints focus border on property panel. */ private static class FocusedPropertyPanel extends PropertyPanel { boolean focused; public FocusedPropertyPanel(PropertyModel model, int preferences) { super(model, preferences); } public void setFocused(boolean focused) { this.focused = focused; } public void paint(Graphics g) { super.paint(g); if (focused) { g.setColor(UIManager.getColor("Button.focus")); // NOI18N g.drawRect(2, 1, getWidth() - 5, getHeight() - 4); } } ////////////////// Accessibility support /////////////////////////////// public AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessibleFocusedPropertyPanel(); } return accessibleContext; } private class AccessibleFocusedPropertyPanel extends AccessibleJComponent { AccessibleFocusedPropertyPanel() {} public AccessibleRole getAccessibleRole() { return AccessibleRole.PANEL; } public String getAccessibleName() { FeatureDescriptor fd = ((ExPropertyModel)getModel()).getFeatureDescriptor(); PropertyEditor editor = getPropertyEditor(); return MessageFormat.format( getString("ACS_PropertyPanelRenderer"), new Object[] { fd.getDisplayName(), (editor == null) ? getString("CTL_No_value") : editor.getAsText() } ); } public String getAccessibleDescription() { FeatureDescriptor fd = ((ExPropertyModel)getModel()).getFeatureDescriptor(); Node node = (Node)((ExPropertyModel)getModel()).getBeans()[0]; Class clazz = getModel().getPropertyType(); return MessageFormat.format( getString("ACSD_PropertyPanelRenderer"), new Object[] { fd.getShortDescription(), clazz == null ? getString("CTL_No_type") : clazz.getName(), node.getDisplayName() } ); } } } private static String getString(String key) { return NbBundle.getBundle(TableSheetCell.class).getString(key); } /** Table cell editor component. Contains special focus control. */ private static class FocusHackedPropertyPanel extends PropertyPanel { public FocusHackedPropertyPanel(PropertyModel model, int preferences) { super(model, preferences); } /** Forward focus request to component that can be focused. */ public void requestFocus() { requestDefaultFocus(); } /** JTable.processKeyBinding forgets to call request focus on cell editor. * So lets do it ourselves. */ public void addNotify() { super.addNotify(); requestFocus(); } } }