edu.ku.brc.af.tasks.subpane.StatsPane.java Source code

Java tutorial

Introduction

Here is the source code for edu.ku.brc.af.tasks.subpane.StatsPane.java

Source

/* Copyright (C) 2015, University of Kansas Center for Research
 * 
 * Specify Software Project, specify@ku.edu, Biodiversity Institute,
 * 1345 Jayhawk Boulevard, Lawrence, Kansas, 66045, USA
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
package edu.ku.brc.af.tasks.subpane;

import static edu.ku.brc.helpers.XMLHelper.getAttr;
import static edu.ku.brc.ui.UIHelper.createDuplicateJGoodiesDef;
import static org.apache.commons.lang.StringUtils.isNotEmpty;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.dom4j.Element;

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

import edu.ku.brc.af.core.AppContextMgr;
import edu.ku.brc.af.core.Taskable;
import edu.ku.brc.af.core.db.DBTableIdMgr;
import edu.ku.brc.af.core.db.DBTableInfo;
import edu.ku.brc.af.core.expresssearch.QueryAdjusterForDomain;
import edu.ku.brc.dbsupport.CustomQueryFactory;
import edu.ku.brc.dbsupport.CustomQueryIFace;
import edu.ku.brc.dbsupport.JPAQuery;
import edu.ku.brc.helpers.XMLHelper;
import edu.ku.brc.specify.datamodel.DataModelObjBase;
import edu.ku.brc.stats.BarChartPanel;
import edu.ku.brc.stats.StatDataItem;
import edu.ku.brc.stats.StatGroupTable;
import edu.ku.brc.stats.StatGroupTableFromCustomQuery;
import edu.ku.brc.stats.StatGroupTableFromQuery;
import edu.ku.brc.stats.StatsMgr;
import edu.ku.brc.ui.CommandAction;
import edu.ku.brc.ui.UIHelper;
import edu.ku.brc.ui.UIRegistry;

/**
 * A class that loads a page of statistics from an XML description
     
 * @code_status Complete
 **
 * @author rods
 *
 */
@SuppressWarnings("serial") //$NON-NLS-1$
public class StatsPane extends BaseSubPane {
    protected enum QueryType {
        SQL, JPA, CUSTOM
    }

    // Static Data Members
    private static final Logger log = Logger.getLogger(StatsPane.class);

    protected static BasicStroke lineStroke = new BasicStroke(1.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);

    // Data Members
    protected String resourceName = null;
    protected Color bgColor = Color.WHITE;
    protected boolean useSeparatorTitles = false;
    protected FadeBtn updateBtn = null;

    protected int PREFERREDWIDTH = 300;
    protected int SPACING = 35;

    protected JComponent upperDisplayComp = null;
    protected JPanel centerPanel = null;
    protected Vector<Component> comps = new Vector<Component>();

    /**
     * Creates a StatsPane.
     * @param name name of pane
     * @param task the owning task
     * @param resourceName the name of the resource that contains the configuration
     * @param useSeparatorTitles indicates the group panels should use separator titles instead of boxes
     * @param bgColor the background color
     * @param upperDisplayComp a display component for the upper half of the screen
    */
    public StatsPane(final String name, final Taskable task, final String resourceName,
            final boolean useSeparatorTitles, final Color bgColor, final JComponent upperDisplayComp) {
        super(name, task);

        this.resourceName = resourceName;
        this.useSeparatorTitles = useSeparatorTitles;
        this.upperDisplayComp = upperDisplayComp;

        if (bgColor != null) {
            this.bgColor = bgColor;
        } else {
            this.bgColor = Color.WHITE;
        }
        setBackground(this.bgColor);
        setOpaque(true);

        setLayout(new BorderLayout());

        if (upperDisplayComp == null) {
            JLabel lbl = UIHelper.createI18NLabel("COLL_STATS", SwingConstants.CENTER);
            int pntSize = lbl.getFont().getSize();
            lbl.setFont(lbl.getFont().deriveFont((float) pntSize + 2).deriveFont(Font.BOLD));
            add(lbl, BorderLayout.NORTH);
        }

        init();

        registerPrintContextMenu();
    }

    /**
     * Converts a string to a QueryType (default conversion is SQL)
     * @param type the string to be converted
     * @return the QueryType
     */
    protected QueryType getQueryType(final String type) {
        try {
            return QueryType.valueOf(type.toUpperCase());

        } catch (Exception ex) {
            log.error(ex);
            edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
            edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(StatsPane.class, ex);
        }
        return QueryType.SQL;
    }

    /**
     * @param command
     * @return
     */
    protected CommandAction createCommandActionFromElement(final Element command) {
        CommandAction cmdAction = null;
        if (command != null) {
            String typeStr = getAttr(command, "type", null); //$NON-NLS-1$
            String actionStr = getAttr(command, "action", null); //$NON-NLS-1$
            String className = getAttr(command, "class", null); //$NON-NLS-1$
            String data = getAttr(command, "data", null); //$NON-NLS-1$

            if (StringUtils.isNotEmpty(typeStr) && StringUtils.isNotEmpty(actionStr)) {
                Class<? extends DataModelObjBase> classObj = null;

                if (StringUtils.isNotEmpty(className)) {
                    try {
                        classObj = Class.forName(className).asSubclass(DataModelObjBase.class);

                    } catch (Exception ex) {
                        edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                        edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(StatsPane.class, ex);

                    }
                    if (classObj != null) {
                        cmdAction = new CommandAction(typeStr, actionStr, classObj);
                    }
                } else {
                    cmdAction = new CommandAction(typeStr, actionStr, data);
                }
            }
        }
        return cmdAction;
    }

    /**
     * @param boxElement
     * @param title
     * @param colNames
     * @return
     */
    protected StatGroupTable processGroupItems(final Element boxElement, final String title,
            final String[] colNames, final boolean hasResBundle) {
        StatGroupTable groupTable = null;

        List<?> items = boxElement.selectNodes("item"); //$NON-NLS-1$
        if (items != null) {
            groupTable = new StatGroupTable(title, colNames, useSeparatorTitles, items.size());
            for (Object io : items) {
                Element itemElement = (Element) io;
                String itemTitle = getStrFromAttr(itemElement, "title", hasResBundle); //$NON-NLS-1$

                String formatStr = null;
                Element formatNode = (Element) itemElement.selectSingleNode("sql/format"); //$NON-NLS-1$
                if (formatNode != null) {
                    formatStr = formatNode.getTextTrim();
                }

                Element command = (Element) itemElement.selectSingleNode("command"); //$NON-NLS-1$
                CommandAction cmdAction = null;
                if (command != null) {
                    cmdAction = createCommandActionFromElement(command);
                }

                Element subSqlEl = (Element) itemElement.selectSingleNode("sql"); //$NON-NLS-1$
                QueryType queryType = getQueryType(getAttr(subSqlEl, "type", "sql")); //$NON-NLS-1$ //$NON-NLS-2$

                CustomQueryIFace customQuery = null;
                if (queryType == QueryType.CUSTOM) {
                    String customQueryName = getAttr(subSqlEl, "name", null); //$NON-NLS-1$
                    if (StringUtils.isNotEmpty(customQueryName)) {
                        customQuery = CustomQueryFactory.getInstance().getQuery(customQueryName);
                        if (customQuery == null) {
                            return null;
                        }

                        if (!isPermissionOK(customQuery.getTableIds())) {
                            return null;
                        }
                    } else {
                        log.error("Name is empty for box item [" + getAttr(itemElement, "title", "N/A") + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
                        return null;
                    }

                } else {
                    List<Integer> tableIds = getTabledIds(subSqlEl);
                    if (!isPermissionOK(tableIds)) {
                        return null;
                    }
                }

                StatDataItem statItem = new StatDataItem(itemTitle, cmdAction,
                        getAttr(itemElement, "useprogress", false)); //$NON-NLS-1$

                //System.out.println("["+queryType+"]");
                switch (queryType) {
                case SQL: {
                    List<?> statements = itemElement.selectNodes("sql/statement"); //$NON-NLS-1$

                    if (statements.size() == 1) {
                        String sql = QueryAdjusterForDomain.getInstance()
                                .adjustSQL(((Element) statements.get(0)).getText());
                        statItem.add(sql, 1, 1, StatDataItem.VALUE_TYPE.Value, formatStr);

                    } else if (statements.size() > 0) {
                        int cnt = 0;
                        for (Object stObj : statements) {
                            Element stElement = (Element) stObj;
                            int vRowInx = getAttr(stElement, "row", -1); //$NON-NLS-1$
                            int vColInx = getAttr(stElement, "col", -1); //$NON-NLS-1$
                            String format = getAttr(stElement, "format", null); //$NON-NLS-1$
                            String sql = QueryAdjusterForDomain.getInstance().adjustSQL(stElement.getText());

                            if (vRowInx == -1 || vColInx == -1) {
                                statItem.add(sql, format); // ignore return object
                            } else {
                                statItem.add(sql, vRowInx, vColInx, StatDataItem.VALUE_TYPE.Value, format); // ignore return object
                            }
                            cnt++;
                        }
                    }
                }
                    break;

                case JPA: {
                    List<?> statements = itemElement.selectNodes("sql/statement"); //$NON-NLS-1$
                    String sql = QueryAdjusterForDomain.getInstance()
                            .adjustSQL(((Element) statements.get(0)).getText());
                    statItem.addCustomQuery(new JPAQuery(sql), formatStr);

                }
                    break;

                case CUSTOM:
                    statItem.addCustomQuery(customQuery, formatStr);
                    break;
                }

                groupTable.addDataItem(statItem);
                statItem.startUp();
            }
            groupTable.relayout();
        }
        return groupTable;
    }

    /**
     * @param str
     * @param hasResBundle
     * @return
     */
    private String getStrFromAttr(final String str, final boolean hasResBundle) {
        if (StringUtils.isNotEmpty(str) && !str.equals(" ")) {
            return hasResBundle ? UIRegistry.getResourceString(str) : str;
        }
        return " ";
    }

    /**
     * @param boxElement
     * @param attr
     * @param hasResBundle
     * @return
     */
    private String getStrFromAttr(final Element boxElement, final String attr, final boolean hasResBundle) {
        String str = getAttr(boxElement, attr, null);
        return getStrFromAttr(str, hasResBundle);
    }

    /**
     * @param boxElement
     * @return
     */
    protected Component processBox(final Element boxElement, final boolean hasResBundle) {
        Component comp = null;

        int descCol = getAttr(boxElement, "desccol", -1); //$NON-NLS-1$
        int valCol = getAttr(boxElement, "valcol", -1); //$NON-NLS-1$
        String descTitle = getStrFromAttr(boxElement, "desctitle", hasResBundle); //$NON-NLS-1$
        String title = getStrFromAttr(boxElement, "title", hasResBundle); //$NON-NLS-1$
        String noresults = getStrFromAttr(boxElement, "noresults", hasResBundle); //$NON-NLS-1$

        //log.debug("***** "+title+" *******");

        String[] colNames = null;
        if (valCol != -1 && descCol == -1) {
            colNames = new String[] { getStrFromAttr(boxElement, "valtitle", hasResBundle) };

        } else if (descCol != -1 && valCol == -1 && StringUtils.isNotEmpty(descTitle)) {
            colNames = new String[] { getStrFromAttr(descTitle, hasResBundle) };

        } else {
            colNames = new String[] { getStrFromAttr(descTitle, hasResBundle),
                    getStrFromAttr(boxElement, "valtitle", hasResBundle) }; //$NON-NLS-1$ //$NON-NLS-2$
        }

        Element sqlElement = (Element) boxElement.selectSingleNode("sql"); //$NON-NLS-1$
        if (valCol > -1 && sqlElement != null) {
            List<Integer> tableIds = getTabledIds(sqlElement);
            if (isPermissionOK(tableIds)) {
                QueryType queryType = getQueryType(getAttr(sqlElement, "type", "sql")); //$NON-NLS-1$ //$NON-NLS-2$

                Element command = (Element) boxElement.selectSingleNode("command"); //$NON-NLS-1$
                int colId = -1;
                CommandAction cmdAction = null;

                if (command != null) {
                    colId = getAttr(command, "colid", -1); //$NON-NLS-1$
                    cmdAction = createCommandActionFromElement(command);
                }

                //System.out.println("["+queryType+"]");
                try {
                    Element sqlStmt = (Element) sqlElement.selectSingleNode("statement"); //$NON-NLS-1$
                    String sql = QueryAdjusterForDomain.getInstance().adjustSQL(sqlStmt.getText());

                    switch (queryType) {
                    case SQL: {
                        sql = QueryAdjusterForDomain.getInstance().adjustSQL(sql);
                        StatGroupTableFromQuery group = new StatGroupTableFromQuery(title, colNames, sql, descCol,
                                valCol, useSeparatorTitles, noresults);
                        if (cmdAction != null) {
                            group.setCommandAction(cmdAction, colId);
                        }
                        comp = group;
                        group.relayout();
                    }
                        break;

                    case JPA: {
                        StatGroupTableFromCustomQuery group = new StatGroupTableFromCustomQuery(title, colNames,
                                new JPAQuery(sql), useSeparatorTitles, noresults);
                        if (cmdAction != null) {
                            group.setCommandAction(cmdAction, colId);
                        }
                        comp = group;
                        group.relayout();
                    }
                        break;

                    case CUSTOM: {
                        StatGroupTableFromCustomQuery group = new StatGroupTableFromCustomQuery(title, colNames,
                                sql, // the name
                                useSeparatorTitles, noresults);
                        if (cmdAction != null) {
                            group.setCommandAction(cmdAction, colId);
                        }
                        comp = group;
                        group.relayout();
                    }
                        break;

                    } // switch

                } catch (Exception ex) {
                    edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                    edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(StatsPane.class, ex);
                    ex.printStackTrace();
                }
            }
            //log.debug("After Relayout: "+group.getPreferredSize()+" "+group.getSize()+" "+group.getComponentCount());

        } else {
            comp = processGroupItems(boxElement, title, colNames, hasResBundle);
        }

        return comp;
    }

    /**
     * Loads all the panels.
     */
    protected void init() {
        JComponent parentComp = upperDisplayComp != null ? upperDisplayComp : this;
        for (Component c : comps) {
            parentComp.remove(c);
        }
        comps.clear();

        if (centerPanel != null) {
            remove(centerPanel);
        }

        Element rootElement = null;
        try {
            rootElement = AppContextMgr.getInstance().getResourceAsDOM(resourceName);
            if (rootElement == null) {
                throw new RuntimeException("Couldn't find resource [" + resourceName + "]"); //$NON-NLS-1$ //$NON-NLS-2$
            }

            // count up rows and column
            StringBuilder rowsDef = new StringBuilder(128);

            boolean hasResBundle = false;
            Element panelEl = (Element) rootElement.selectObject("/panel");
            String resBundleName = XMLHelper.getAttr(panelEl, "resource", null);
            if (StringUtils.isNotEmpty(resBundleName)) {
                hasResBundle = true;
                UIRegistry.loadAndPushResourceBundle(resBundleName);
            }

            List<?> rows = rootElement.selectNodes("/panel/row"); //$NON-NLS-1$
            int maxCols = 0;
            for (Object obj : rows) {
                Element rowElement = (Element) obj;
                List<?> boxes = rowElement.selectNodes("box"); //$NON-NLS-1$
                maxCols = Math.max(maxCols, boxes.size());
                if (rowsDef.length() > 0) {
                    rowsDef.append(",15dlu,"); //$NON-NLS-1$
                }
                rowsDef.append("top:p"); //$NON-NLS-1$
            }

            int preferredWidth = PREFERREDWIDTH;
            int spacing = SPACING;

            String colDefs = createDuplicateJGoodiesDef("f:min(" + preferredWidth + "px;p)", spacing + "px", //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
                    maxCols);
            FormLayout formLayout = new FormLayout(colDefs, rowsDef.toString());
            PanelBuilder builder = new PanelBuilder(formLayout);
            CellConstraints cc = new CellConstraints();

            int y = 1;
            for (Object obj : rows) {
                Element rowElement = (Element) obj;

                int x = 1;
                List<?> boxes = rowElement.selectNodes("box"); //$NON-NLS-1$
                for (Object bo : boxes) {
                    Element boxElement = (Element) bo;

                    String type = getAttr(boxElement, "type", "box"); //$NON-NLS-1$ //$NON-NLS-2$
                    int colSpan = getAttr(boxElement, "colspan", 1); //$NON-NLS-1$

                    Component comp = null;
                    if (type.equalsIgnoreCase("bar chart")) //$NON-NLS-1$
                    {
                        String statName = getAttr(boxElement, "name", null); //$NON-NLS-1$

                        if (isNotEmpty(statName)) {
                            BarChartPanel bcp = (BarChartPanel) StatsMgr.createStatPane(statName);
                            int width = colSpan > 1 ? ((maxCols * preferredWidth) + ((maxCols - 1) * spacing))
                                    : preferredWidth;
                            // We start by assuming the chart will be square which is why we use
                            // preferredWidth as the height, and then we calculate the new width
                            bcp.setPreferredChartSize(width, preferredWidth);
                            comp = bcp;
                            //comp.setSize(new Dimension(preferredWidth, preferredWidth));
                            //comp.setPreferredSize(new Dimension(preferredWidth, preferredWidth));
                            //comp.invalidate();
                            //comp.doLayout();
                            //System.out.println(comp.getSize());
                            validate();
                            doLayout();
                            repaint();
                        }

                    } else // The default is "Box"
                    {
                        comp = processBox(boxElement, hasResBundle);
                    }

                    if (comp != null) {
                        comps.add(comp);

                        if (colSpan == 1) {
                            builder.add(comp, cc.xy(x, y));

                        } else {
                            builder.add(comp, cc.xywh(x, y, colSpan, 1));
                        }
                        x += 2;
                    }
                } // boxes
                y += 2;
            }

            if (hasResBundle) {
                UIRegistry.popResourceBundle();
            }

            setBackground(bgColor);

            JPanel statPanel = builder.getPanel();
            statPanel.setBackground(Color.WHITE);

            boolean hasUpper = upperDisplayComp != null;

            builder = new PanelBuilder(new FormLayout("C:P:G", hasUpper ? "50px,p,20px,p" : "p")); //$NON-NLS-1$ //$NON-NLS-2$

            if (hasUpper) {
                y = 2;
                builder.add(upperDisplayComp, cc.xy(1, y));
                y += 2;

            } else {
                y = 1;
            }

            builder.add(statPanel, cc.xy(1, y));
            centerPanel = builder.getPanel();

            centerPanel.setBackground(Color.WHITE);

            //For Tiling
            if (isTiled()) {
                centerPanel.setOpaque(false);
                setOpaque(false);
                statPanel.setOpaque(false);
            }

            centerPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

            add(centerPanel, BorderLayout.CENTER);

            if (updateBtn == null) {
                PanelBuilder pb = new PanelBuilder(new FormLayout("f:p:g,p,4px", "4px,p,4px"));
                //pb.setOpaque(false);
                updateBtn = new FadeBtn(UIRegistry.getResourceString("STS_UPDATE"));
                pb.add(updateBtn, cc.xy(2, 2));
                pb.getPanel().setBackground(bgColor);
                add(pb.getPanel(), BorderLayout.SOUTH);

                updateBtn.addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        init();
                    }
                });
            }

            centerPanel.validate();
            validate();
            doLayout();

        } catch (Exception ex) {
            edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
            edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(StatsPane.class, ex);
            log.error(ex);
            ex.printStackTrace();
        }

    }

    /**
     * @param item
     * @return
     */
    protected static List<Integer> getTabledIds(final Element item) {
        if (item != null) {
            List<?> tables = item.selectNodes("tables/id");//$NON-NLS-1$
            if (tables != null) {
                List<Integer> list = new Vector<Integer>();
                for (Object obj : tables) {
                    Element tbl = (Element) obj;
                    list.add(Integer.parseInt(tbl.getTextTrim()));
                }
                return list;
            }
        }
        return null;
    }

    public static boolean isPermissionOK(final List<Integer> list) {
        // not sure if the default should be true or false
        // certainly is security is off it should return true.
        boolean isOK = true;
        if (AppContextMgr.isSecurityOn()) {
            if (list != null) {
                for (Integer tableId : list) {
                    DBTableInfo tInfo = DBTableIdMgr.getInstance().getInfoById(tableId);
                    if (tInfo != null) {
                        if (!tInfo.getPermissions().canView()) {
                            return false;
                        }
                    }
                }
            }
        }
        return isOK;
    }

    class FadeBtn extends JComponent {
        protected String text;
        protected Dimension size = null;
        protected boolean isHover = false;
        protected boolean isActivate = false;

        public FadeBtn(final String text) {
            this.text = text;

            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseEntered(MouseEvent e) {
                    isHover = true;
                    repaint();
                }

                @Override
                public void mouseExited(MouseEvent e) {
                    isHover = false;
                    repaint();
                }

                @Override
                public void mousePressed(MouseEvent e) {
                    isActivate = true;
                    repaint();
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    isActivate = false;
                    repaint();
                }
            });
        }

        /* (non-Javadoc)
         * @see edu.ku.brc.ui.JTiledPanel#paintComponent(java.awt.Graphics)
         */
        @Override
        protected void paintComponent(final Graphics g) {
            super.paintComponent(g);

            Dimension sz = getSize();

            int x = 0;
            int y = 0;

            Graphics2D g2d = (Graphics2D) g;

            Color color;
            if (isActivate) {
                color = new Color(64, 64, 255, 192);
            } else if (isHover) {
                color = new Color(32, 32, 32, 192);
            } else {
                color = new Color(128, 128, 128, 128);
            }
            g.setColor(color);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            int arc = sz.height;
            RoundRectangle2D.Double rr = new RoundRectangle2D.Double(x, y, sz.width - 1, sz.height - 1, arc, arc);
            g2d.setStroke(lineStroke);
            g2d.draw(rr);

            FontMetrics fm = g2d.getFontMetrics();
            int hgt = fm.getHeight();
            int wdh = fm.stringWidth(text);
            g2d.drawString(text, ((size.width - wdh) / 2),
                    size.height - ((size.height - hgt) / 2) - fm.getDescent() - 1);

        }

        /* (non-Javadoc)
         * @see javax.swing.JComponent#getPreferredSize()
         */
        @Override
        public Dimension getPreferredSize() {
            if (size == null) {
                // This is lame, but practical
                BufferedImage bufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
                Graphics2D g2d = bufferedImage.createGraphics();
                FontMetrics fm = g2d.getFontMetrics();
                int h = fm.getHeight() + 4;
                int w = fm.stringWidth(text) + h;
                size = new Dimension(w, h);
            }

            return size;
        }
    }
}