/*
 * This file is part of Herschel Common Science System (HCSS).
 * Copyright 2001-2010 Herschel Science Ground Segment Consortium
 *
 * HCSS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * HCSS 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with HCSS.
 * If not, see .
 */
package org.jplot2d.swing.proptable;


import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyEditor;

import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;

import org.jplot2d.swing.proptable.cellrenderer.TableCellRendererFactory;
import org.jplot2d.swing.proptable.cellrenderer.TableCellRendererRegistry;
import org.jplot2d.swing.proptable.editor.PropertyEditorFactory;
import org.jplot2d.swing.proptable.editor.PropertyEditorRegistry;
import org.jplot2d.swing.proptable.property.Property;

/**
 * 
 * @author Jingjing Li
 */
public class PropertyTable extends JTable {

    private static final int HOTSPOT_SIZE = 18;

    private static final String TREE_EXPANDED_ICON_KEY = "Tree.expandedIcon";

    private static final String TREE_COLLAPSED_ICON_KEY = "Tree.collapsedIcon";

    private static final String PANEL_BACKGROUND_COLOR_KEY = "Panel.background";

    private static class CellBorder implements Border {

        private int indentWidth; // space before hotspot

        private boolean showToggle;

        private boolean toggleState;

        private Color bgColor;

        private Icon expandedIcon = (Icon) UIManager
                .get(TREE_EXPANDED_ICON_KEY);

        private Icon collapsedIcon = (Icon) UIManager
                .get(TREE_COLLAPSED_ICON_KEY);

        private Insets insets = new Insets(1, 0, 1, 1);

        private boolean isProperty;

        public void configure(PropertyTable table, PropertyTableItem item) {
            isProperty = item.isProperty();
            toggleState = item.isVisible();
            showToggle = item.hasToggle();
            if (!isProperty) {
                bgColor = table.getGridColor();
            }
            indentWidth = getIndent(item);
            insets.left = indentWidth + (showToggle ? HOTSPOT_SIZE : 0) + 2;

        }

        public Insets getBorderInsets(Component c) {
            return insets;
        }

        public void paintBorder(Component c, Graphics g, int x, int y,
                int width, int height) {
            if (!isProperty) {
                Color oldColor = g.getColor();
                g.setColor(bgColor);
                g.fillRect(x, y, x + HOTSPOT_SIZE - 2, y + height);
                g.setColor(oldColor);
            }

            if (showToggle) {
                Icon drawIcon = (toggleState ? expandedIcon : collapsedIcon);
                drawIcon.paintIcon(c, g, x + indentWidth
                        + (HOTSPOT_SIZE - 2 - drawIcon.getIconWidth()) / 2, y
                        + (height - drawIcon.getIconHeight()) / 2);
            }
        }

        public boolean isBorderOpaque() {
            return true;
        }

    }

    /**
     * A {@link TableCellRenderer} for property names.
     */
    private static class NameRenderer extends DefaultTableCellRenderer {

        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        private CellBorder border;

        public NameRenderer() {
            super();
            border = new CellBorder();
        }

        public Component getTableCellRendererComponent(JTable table,
                Object value, boolean isSelected, boolean hasFocus, int row,
                int column) {
            super.getTableCellRendererComponent(table, value, isSelected,
                    hasFocus, row, column);
            setBorder(border);

            PropertyTableItem item = (PropertyTableItem) value;
            // configure the border
            border.configure((PropertyTable) table, item);

            if (item.isProperty()) {
                if (isSelected) {
                    setBackground(table.getSelectionBackground());
                    setForeground(table.getSelectionForeground());
                } else {
                    setBackground(table.getBackground());
                    setForeground(table.getForeground());
                }
            } else {
                if (isSelected) {
                    setBackground(table.getSelectionBackground());
                    setForeground(table.getSelectionForeground());
                } else {
                    setBackground(table.getGridColor());
                    setForeground(table.getGridColor().darker());
                }
            }

            setEnabled(isSelected || !item.isProperty() ? true : item
                    .getProperty().isEditable());
            setFont(getFont().deriveFont(
                    item.isProperty() ? Font.PLAIN : Font.BOLD));
            setText(item.getName());

            return this;
        }
    }

    private static class CategoryValueRenderer extends DefaultTableCellRenderer {

        private static final long serialVersionUID = 1L;

        public Component getTableCellRendererComponent(JTable table,
                Object value, boolean isSelected, boolean hasFocus, int row,
                int column) {
            if (isSelected) {
                setBackground(table.getSelectionBackground());
                setForeground(table.getSelectionForeground());
            } else {
                setBackground(table.getGridColor());
                setForeground(table.getGridColor().darker());
            }
            setText("");
            return this;
        }
    }

    private static class CategoryVisibilityToggle extends MouseAdapter {
        public void mouseReleased(MouseEvent event) {
            PropertyTable table = (PropertyTable) event.getComponent();
            int row = table.rowAtPoint(event.getPoint());
            int column = table.columnAtPoint(event.getPoint());
            if (row != -1 && column == 0) {
                // if we clicked on an Item, see if we clicked on its hotspot
                PropertyTableItem item = table.getModel().getPropertyTableItem(
                        row);
                int x = event.getX() - getIndent(item);
                if (x > 0 && x < HOTSPOT_SIZE)
                    item.toggle();
            }
        }
    }

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    private TableCellRenderer categoryValueRenderer;

    private TableCellRenderer nameRenderer;

    private TableCellRendererFactory rendererFactory;

    private PropertyEditorFactory editorFactory;

    public PropertyTable() {
        super(new PropertyTableModel());

        // hide the table header, we do not need it
        Dimension nullSize = new Dimension(0, 0);
        getTableHeader().setPreferredSize(nullSize);
        getTableHeader().setMinimumSize(nullSize);
        getTableHeader().setMaximumSize(nullSize);
        getTableHeader().setVisible(false);

        // table header not being visible, make sure we can still resize the
        // columns
        new HeaderlessColumnResizer(this);

        // setRowHeight(22);

        setGridColor(UIManager.getColor(PANEL_BACKGROUND_COLOR_KEY));
        setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        setColumnSelectionAllowed(false);
        setRowSelectionAllowed(true);
        setFillsViewportHeight(true);
        setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
        addMouseListener(new CategoryVisibilityToggle());

        nameRenderer = new NameRenderer();
        categoryValueRenderer = new CategoryValueRenderer();

        // force the JTable to commit the edit when it loose focus
        putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);

        // default renderers and editors
        setTableCellRendererFactory(new TableCellRendererRegistry());
        setEditorFactory(new PropertyEditorRegistry());
    }

    public TableCellRenderer getCellRenderer(int row, int column) {

        PropertyTableModel model = getModel();
        PropertyTableItem item = model.getPropertyTableItem(row);

        switch (column) {
        case PropertyTableModel.NAME_COLUMN:
            // name column gets a custom renderer
            ((JComponent) nameRenderer).setToolTipText(item.getToolTipText());
            return nameRenderer;

        case PropertyTableModel.VALUE_COLUMN: {
            if (!item.isProperty())
                return categoryValueRenderer;

            // property value column gets the renderer from the factory, but
            // wrapped
            Property property = item.getProperty();
            TableCellRenderer renderer = getTableCellRendererFactory()
                    .createTableCellRenderer(property);

            Class type = getWrapperClass(property.getType());
            if (renderer == null) {
                renderer = getDefaultRenderer(type);
            }
            if (renderer instanceof JCheckBox) {
                ((JCheckBox) renderer).setHorizontalAlignment(JCheckBox.LEFT);
                ((JCheckBox) renderer).setEnabled(property.isEditable());
            }
            return renderer;
        }
        default:
            // when will this happen, given the above?
            return super.getCellRenderer(row, column);
        }

    }

    public TableCellEditor getCellEditor(int row, int column) {

        if (column == 0) {
            return null;
        }

        PropertyTableItem item = getModel().getPropertyTableItem(row);
        if (!item.isProperty())
            return null;

        Property property = item.getProperty();
        PropertyEditor editor = getEditorFactory().createPropertyEditor(
                property);

        if (editor != null) {
            return new PropertyCellEditor(editor);
        }

        return null;
    }

    public PropertyTableModel getModel() {
        return (PropertyTableModel) super.getModel();
    }

    public void setTableCellRendererFactory(TableCellRendererFactory factory) {
        rendererFactory = factory;
    }

    public TableCellRendererFactory getTableCellRendererFactory() {
        return rendererFactory;
    }

    public void setEditorFactory(PropertyEditorFactory factory) {
        editorFactory = factory;
    }

    public final PropertyEditorFactory getEditorFactory() {
        return editorFactory;
    }

    static int getIndent(PropertyTableItem item) {
        int indent = 0;

        if (item.isProperty()) {
            // it is a property, it has no parent or a category, and no child
            if ((item.getParent() == null || !item.getParent().isProperty())
                    && !item.hasToggle()) {
                indent = HOTSPOT_SIZE;
            } else {
                // it is a property with children
                if (item.hasToggle()) {
                    indent = item.getDepth() * HOTSPOT_SIZE;
                } else {
                    indent = (item.getDepth() + 1) * HOTSPOT_SIZE;
                }
            }

            indent += HOTSPOT_SIZE;

        } else {
            // category has no indent
            indent = 0;
        }
        return indent;
    }

    protected void createDefaultRenderers() {
        super.createDefaultRenderers();
        /*
         * remove the internal renderer for Float & Double, because they use
         * NumberFormat to convert to String, which convert NaN into an
         * undisplayable character.
         */
        setDefaultRenderer(Float.class, null);
        setDefaultRenderer(Double.class, null);
    }

    private Class getWrapperClass(Class primitive) {
        if (primitive == byte.class) {
            return Byte.class;
        } else if (primitive == short.class) {
            return Short.class;
        } else if (primitive == int.class) {
            return Integer.class;
        } else if (primitive == long.class) {
            return Long.class;
        } else if (primitive == float.class) {
            return Float.class;
        } else if (primitive == double.class) {
            return Double.class;
        } else if (primitive == boolean.class) {
            return Boolean.class;
        } else if (primitive == char.class) {
            return Character.class;
        }
        return primitive;
    }

}
 

Ads help maintain this website.