Package DisplayProject

Source Code of DisplayProject.RadioListUI

/*
Copyright (c) 2003-2008 ITerative Consulting Pty Ltd. All Rights Reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:

o Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.
 
o Redistributions in binary form must reproduce the above copyright notice, this list of conditions
and the following disclaimer in the documentation and/or other materials provided with the distribution.
   
o This jcTOOL Helper Class software, whether in binary or source form may not be used within,
or to derive, any other product without the specific prior written permission of the copyright holder

 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


*/
package DisplayProject;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.ActionMap;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JRadioButton;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.ListUI;
import javax.swing.plaf.basic.BasicListUI;

import sun.swing.UIAction;
import Framework.ErrorMgr;
import Framework.ListElement;
import Framework.UsageException;

import com.sun.java.swing.plaf.windows.WindowsLookAndFeel;

/**
* This class packs radio lists in a manner very similar to the manner Forte did, catering
* for varying wrap sizes, horizontal and vertical lists and so on. It handles all the drawing
* and the keyboard navigation between elements within the cell.
*
@author Tim
*/
public class RadioListUI extends BasicListUI {
    /**
     * The number of columns in the list. This is derived from the control's wrap width
     */
    protected int columnCount = 1;

    /**
     * The radio list we're mapping to
     */
    protected RadioList radioList;

    /**
     * The listener for property changes, custom to this radio list UI
     */
    protected PropertyChangeListener propertyListener = null;

    /**
     * Ensure we can tell our superclass that it's layout has changed.
     */
    private final static int layoutOrientationChanged = 1 << 7;

    /**
     * The width of each column in the radio list
     */
    private int[] columnWidths = new int[0];

    /**
     * The size of the the radio list, to make it easy to tell if the size changes and we need to re-compute things
     */
    private int listHeight = -1;
    private int listWidth = -1;

    public RadioListUI() {
    }

    public static RadioListUI createUI(JComponent c) {
        return new RadioListUI();
    }

    /**
     * Install the component and remember the radio list
     */
    @Override
    public void installUI(JComponent c) {
        radioList = (RadioList)c;
        radioList.setCellRenderer(new RadioListCellRenderer(radioList));
        super.installUI(c);
    }

    /**
     * Install the listeners for the properties this UI depends on, such as wrap size
     */
    @Override
    protected void installListeners() {
        super.installListeners();
        this.propertyListener = new RadioListUIPropertyHandler();
        radioList.addPropertyChangeListener(this.propertyListener);
    }

    /**
     * Uninstall our listeners
     */
    @Override
    protected void uninstallListeners() {
        super.uninstallListeners();
        radioList.removePropertyChangeListener(this.propertyListener);
        propertyListener = null;
    }

    /**
     * Uninstall this UI crom the passed component correctly
     */
    @Override
    public void uninstallUI(JComponent c) {
        super.uninstallUI(c);
        listHeight = -1;
        listWidth = -1;
    }

    /**
     * Recalculate the display column widths
     */
    protected void recalculateColumnWidths() {
        if (this.columnWidths == null || this.columnWidths.length != columnCount) {
            this.columnWidths = new int[columnCount];
        }
        // clear existing values
        for (int i = 0; i < this.columnWidths.length; i++) {
            this.columnWidths[i] = 0;
        }

        if (!(list.getCellRenderer() instanceof RadioListCellRenderer)) {
            return;
        }
        RadioListCellRenderer renderer = (RadioListCellRenderer)list.getCellRenderer();
        int size = list.getModel().getSize();

        // We always have a fixed height, so set it here
        if (size > 0) {
            radioList.setFixedCellHeight(renderer.getPreferredSize(list.getModel().getElementAt(0)).height);
        }

        for (int i = 0; i < size; i++) {
            Object value = list.getModel().getElementAt(i);
            Dimension d = renderer.getPreferredSize(value);

            int column = convertModelToColumn(i);

            // Check to stop Swing Designer from crashing. CraigM: 02/08/2007
            if (column < columnWidths.length) {
                columnWidths[column] = Math.max(columnWidths[column], d.width);
            }
        }

        // This is currently fine for packed. If we're using constant, set them all to be the same width
        if (radioList.layoutPolicy != Constants.LP_PACKED) {
            int maxSize = 0;
            for (int i = 0; i < this.columnWidths.length; i++) {
                if (this.columnWidths[i] > maxSize) {
                    maxSize = this.columnWidths[i];
                }
            }
            for (int i = 0; i < this.columnWidths.length; i++) {
                this.columnWidths[i] = maxSize;
            }
        }
    }

    /**
     * @return The bounds of the rectange encompassing the cells index1
     * and index2. If these are on different rows the whole rows are
     * selected, or if they're on different columns the whole columns
     * are selected
     * @see ListUI#getCellBounds
     */
    @Override
    public Rectangle getCellBounds(JList list, int index1, int index2) {
        maybeUpdateLayoutState();

        int minIndex = Math.min(index1, index2);
        int maxIndex = Math.max(index1, index2);

        if (minIndex >= list.getModel().getSize()) {
            return null;
        }

        Rectangle minBounds = getCellBounds((RadioList)list, minIndex);

        if (minBounds == null) {
            return null;
        }
        if (minIndex == maxIndex) {
            return minBounds;
        }
        Rectangle maxBounds = getCellBounds((RadioList)list, maxIndex);

        if (maxBounds != null) {
            if (((RadioList)list).getLayoutOrientation() == JList.HORIZONTAL_WRAP) {
                int minRow = convertModelToRow(minIndex);
                int maxRow = convertModelToRow(maxIndex);

                if (minRow != maxRow) {
                    minBounds.x = 0;
                    minBounds.width = list.getWidth();
                }
            }
            else if (minBounds.x != maxBounds.x) {
                // Different columns
                minBounds.y = 0;
                minBounds.height = list.getHeight();
            }
            minBounds.add(maxBounds);
        }
        return minBounds;
    }

    /**
     * Gets the bounds of the specified model index, returning the resulting
     * bounds, or null if <code>index</code> is not valid.
     */
    private Rectangle getCellBounds(RadioList list, int index) {
        maybeUpdateLayoutState();

        int row = convertModelToRow(index);
        int column = convertModelToColumn(index);

        if (row == -1 || column == -1) {
            return null;
        }

        Insets insets = list.getInsets();
        int x = insets.left;
        int w = columnWidths[column];
        int y = insets.top + radioList.getFixedCellHeight() * row;
        int h = radioList.getFixedCellHeight();

        for (int i = 0; i < column; i++) {
            x += columnWidths[i];
        }
        return new Rectangle(x, y, w, h);
    }

    /**
     * Returns the row that the model index <code>index</code> will be
     * displayed in. Both index and the return value are zero based
     */
    private int convertModelToRow(int index) {
        int size = list.getModel().getSize();

        if ((index < 0) || (index >= size)) {
            return -1;
        }
        switch (radioList.orientation) {
            case Constants.FO_HORIZONTAL:
                return index % radioList.wrapSize;

            default:
                return index / radioList.wrapSize;
        }
    }

    /**
     * Returns the column that the model index <code>index</code> will be
     * displayed in, based on the wrap type, orientation and packing.
     * Both index and the return value are zero based.<p>
     * Note that this is coded to be the same as Forte, not necessarily to make the most sense.
     */
    private int convertModelToColumn(int index) {
        int size = list.getModel().getSize();

        if ((index < 0) || (index >= size)) {
            return -1;
        }
        switch (radioList.orientation) {
            case Constants.FO_HORIZONTAL:
                return index / radioList.wrapSize;

            default:
                return index % radioList.wrapSize;
        }
    }

    /**
     * Returns the closest cell to the passed in location.
     * Overridden so as to take into consideration our adjustments made to
     * the display of the RadioList cells in getCellBounds(...)
     */
    public int locationToIndex(JList list, Point location) {
        maybeUpdateLayoutState();
        int row = convertLocationToRow(location.x, location.y);
        int column = convertLocationToColumn(location.x, location.y);

        if (row >= 0 && column >= 0) {
            return getModelIndex(column, row);
        }
        return -1;
    }

    /**
     * Returns the model index for the specified display location.
     * If <code>column</code>x<code>row</code> is beyond the length of the
     * model, this will return the model size - 1. This assumes the row
     * and column are 0-based indicies
     */
    private int getModelIndex(int column, int row) {
        switch (radioList.orientation) {
            case Constants.FO_HORIZONTAL:
                return Math.max( 0, Math.min(column * radioList.wrapSize + row, radioList.getModel().getSize()-1) );

            default:
                return Math.max(0, Math.min(row * radioList.wrapSize + column, radioList.getModel().getSize()-1) );
        }
    }

    /**
     * Returns the model index for the specified display location.
     * If <code>column</code>x<code>row</code> is beyond the length of the
     * model, this will return 0. This assumes the row
     * and column are 0-based indicies
     */
    private int getModelIndexWithWrapping(int column, int row) {
        int maxIndex = radioList.getModel().getSize()-1;
        int newIndex;
        switch (radioList.orientation) {
            case Constants.FO_HORIZONTAL:
                newIndex = column * radioList.wrapSize + row;
                break;

            default:
                newIndex = row * radioList.wrapSize + column;
        }
        if (newIndex < 0) newIndex = maxIndex;
        else if (newIndex > maxIndex) newIndex = 0;
        return newIndex;
    }

    /**
     * Returns the closest column to the passed in location.
     */
    private int convertLocationToColumn(int x, int y) {
        Insets insets = list.getInsets();
        x -= insets.left;
        for (int i = 0; i < columnWidths.length; i++) {
            if (x <= columnWidths[i]) {
                return i;
            }
            x -= columnWidths[i];
        }
        if (x > 0) {
            return columnCount - 1;
        }
        else {
            return 0;
        }
    }

    /**
     * Returns the closest row to the passed in location.
     */
    private int convertLocationToRow(int x, int y) {
        Insets insets = list.getInsets();
        y -= insets.top;
        if (y < 0) {
            return 0;
        }
        else {
            int row = y / radioList.getFixedCellHeight();
            int size = 0;
            switch (radioList.orientation) {
                case Constants.FO_HORIZONTAL:
                    size = radioList.wrapSize;
                    break;
                default:
                    size = radioList.getModel().getSize() / radioList.wrapSize;
                    break;
            }

            return Math.min(row, size);
        }
    }

    /**
     * Update the layout state -- set the column widths and then remember the height and width
     */
    protected void updateLayoutState() {
        this.recalculateColumnWidths();
        listHeight = list.getHeight();
        listWidth = list.getWidth();
    }

    /**
     * Paint the rows that intersect the Graphics objects clipRect.  This
     * method calls paintCell as necessary.  Subclasses
     * may want to override these methods.
     *
     * @see #paintCell
     */
    public void paint(Graphics g, JComponent c)
    {
        if (list.getHeight() != listHeight || list.getWidth() != listWidth) {
            updateLayoutStateNeeded |= BasicListUI.fixedCellHeightChanged;
            list.revalidate();
            list.repaint();
        }
        maybeUpdateLayoutState();

        ListCellRenderer renderer = list.getCellRenderer();
        ListModel dataModel = list.getModel();
        ListSelectionModel selModel = list.getSelectionModel();

        if (renderer == null || dataModel.getSize() == 0) {
            return;
        }

        // Determine how many columns we need to paint
        Rectangle paintBounds = g.getClipBounds();

        int startColumn, endColumn;
        startColumn = convertLocationToColumn(paintBounds.x, paintBounds.y);
        endColumn = convertLocationToColumn(paintBounds.x + paintBounds.width, paintBounds.y + paintBounds.height);


        int startRow, endRow;
        startRow = convertLocationToRow(paintBounds.x, paintBounds.y);
        endRow = convertLocationToRow(paintBounds.x + paintBounds.width, paintBounds.y + paintBounds.height);

        int leadIndex = list.getLeadSelectionIndex();

        for (int colCounter = startColumn; colCounter <= endColumn; colCounter++) {
            for (int rowCounter = startRow; rowCounter <= endRow; rowCounter++) {
                int index = getModelIndex(colCounter, rowCounter);
                Rectangle rowBounds = getCellBounds(list, index, index);

                if (rowBounds == null) {
                    // Not valid, bail!
                    continue;
                }
                g.setClip(rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height);
                g.clipRect(paintBounds.x, paintBounds.y, paintBounds.width, paintBounds.height);
                paintCell(g, index, rowBounds, renderer, dataModel, selModel, leadIndex);
            }
        }
    }

    /**
     * Our size is the larger of our title and all our children
     */
    public Dimension getPreferredSize(JComponent c) {
        maybeUpdateLayoutState();

        // If there's a border on the title and the border is not null, the default minimum size will
        // return a border with no pixels gap remaining at the right hand edge. We need to compensate for this.
        TitledBorder tb = (TitledBorder)c.getBorder();
        Dimension d;
        if (tb != null) {
            d = tb.getMinimumSize(c);
            if (tb.getTitle() != null && !"".equals(tb.getTitle())) {
                d.width += 10;
            }
        }
        else {
            d = new Dimension(0, 0);
        }

        int rows = getNumberOfRows();
        Insets ins = radioList.getInsets();
        int height = rows * radioList.getFixedCellHeight() + (ins.bottom + ins.top);
        int width = ins.left + ins.right;
        for (int i = 0; i < columnWidths.length; i++) {
            width += columnWidths[i];
        }
        return new Dimension((int)Math.max(d.getWidth(), width), (int)Math.max(d.getHeight(), height));
    }

    /**
     * Obtain the number of rows in this set of radio buttons
     * @return the number of rows
     */
    public int getNumberOfRows() {
        switch (radioList.orientation) {
            case Constants.FO_HORIZONTAL:
                return radioList.wrapSize;

            default:
                return ((radioList.getModel().getSize() - 1) / radioList.getWrapSize()) + 1;
        }
    }

    /**
     * Obtain the number of columns in this set of radio buttons
     * @return the number of columns
     */
    public int getNumberOfColumns() {
        switch (radioList.orientation) {
            case Constants.FO_HORIZONTAL:
                return ((radioList.getModel().getSize() - 1) / radioList.getWrapSize()) + 1;

            default:
                return radioList.wrapSize;
        }
    }

    /**
     * We also need to know when list selection events happen, so we can cause the right buttons to repaint
     */
    protected ListSelectionListener createListSelectionListener() {
        return new MySelectionListener();
    }

    /**
     * If the list selection has changed, we need to repaint the appropriate parts of the GUI that have changed.
     * This inner class listens for the change events and re-paints appropriate parts of the screen
     * @author tfaulkes
     *
     */
    public class MySelectionListener extends ListSelectionHandler {
        public void valueChanged(ListSelectionEvent e) {
            // We need to repaint both cells in the selection range
            for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
                Rectangle bounds = getCellBounds(list, i, i);
                if (bounds != null) {
                    list.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
                }
            }

            // Also cause the AfterValueChange event to be fired, if a genuine selection change
            if(e.getFirstIndex() != e.getLastIndex() && !e.getValueIsAdjusting()) {
//                ((RadioList)list).unconditionallyFireSelectionValueChanged(e.getFirstIndex(), e.getLastIndex(), e.getValueIsAdjusting());
            }
        }
    }

    /**
     * Install our own keyboard action so we can implement the desired wrapping behavior
     * (going right when at the end of the list goes to the start of the list, etc).
     */
    protected void installKeyboardActions() {
        super.installKeyboardActions();
        ActionMap am = (ActionMap)UIManager.get("List.actionMap");
        wrap(am, "selectPreviousColumn");
        wrap(am, "selectNextColumn");
        wrap(am, "selectPreviousRow");
        wrap(am, "selectNextRow");
        wrap(am, "addToSelection");
    }

    protected void uninstallKeyboardActions() {
        // undo our wrappers
        ActionMap am = (ActionMap)UIManager.get("List.actionMap");
        unwrap(am, "selectPreviousColumn");
        unwrap(am, "selectNextColumn");
        unwrap(am, "selectPreviousRow");
        unwrap(am, "selectNextRow");
        unwrap(am, "addToSelection");
        super.uninstallKeyboardActions();
    }

    private void wrap(ActionMap am, String name) {
        UIAction action = (UIAction)am.get(name);
        if(!(action instanceof ActionWrapper)) {
            ActionWrapper wrapper = new ActionWrapper(name, action);
            am.put(name, wrapper);
        }
    }

    private void unwrap(ActionMap am, String name) {
        UIAction action = (UIAction)am.get(name);
        if(action instanceof ActionWrapper) {
            UIAction original = ((ActionWrapper)action).originalAction;
            am.put(name, original);
        }
    }

    /**
     * This class implements the same focus traversal policy within a radio list set of buttons as Forte has. The key traversals
     * of up, down, left and right are all handled by this class.
     * @author tfaulkes
     */
    private static class ActionWrapper extends UIAction {
        UIAction originalAction = null;

        private ActionWrapper(String name, UIAction original) {
            super(name);
            originalAction = original;
        }

        /**
         * Determine what to do when a key stroke is pressed. This is used to handle the arrow keys to correctly
         * move the highlight and current selection around the radio lists.
         */
        public void actionPerformed(ActionEvent e) {
            String name = getName();
            JList list = (JList)e.getSource();
            ListUI ui = list.getUI();
            if(ui instanceof RadioListUI) {
                RadioListUI currUI = (RadioListUI)ui;
                int index = list.getLeadSelectionIndex();
                int size = list.getModel().getSize();
                int currentRow = currUI.convertModelToRow(index);
                int currentCol = currUI.convertModelToColumn(index);
                int rows = currUI.getNumberOfRows();
                int cols = currUI.getNumberOfColumns();

                // If we don't have anything selected, use the last selected index.  CraigM: 25/03/2008
                if ((currentRow == -1 || currentCol == -1) && list instanceof RadioList) {
                  // TF:17/02/2010:AXA-16:We cannot use the radioList as part of the static class, and this will prevent the UIAction
                  // "hanging on" to an instance of the class. (Although it should hang onto only one instance so it's not
                  // really a memory leak, but it's poor anyway. Also, as the UIAction is shared between instances, there is
                  // no guarantee that this radioList is ithe correct list anyway...
//                    currentRow = currUI.convertModelToRow(radioList.getLastSelectedIndex());
//                    currentCol = currUI.convertModelToColumn(radioList.getLastSelectedIndex());
                    currentRow = currUI.convertModelToRow(((RadioList)list).getLastSelectedIndex());
                    currentCol = currUI.convertModelToColumn(((RadioList)list).getLastSelectedIndex());
                }

                if (name.equals("addToSelection")) {
                    // Only select one if there ins't one there already
                    if (size > 0 && list.getSelectedIndex() == -1) {
                        index = 0;
                    }
                }
                else if(name.equals("selectPreviousRow")) {
                    if (currentRow > 0) {
                        index = currUI.getModelIndexWithWrapping(currentCol, currentRow - 1);
                    }
                    else if (currentCol > 0){
                        index = currUI.getModelIndexWithWrapping(currentCol - 1, rows - 1);
                    }
                    else {
                        index = size - 1;
                    }
                }
                else if (name.equals("selectPreviousColumn")) {
                    if (currentCol > 0) {
                        index = currUI.getModelIndexWithWrapping(currentCol - 1, currentRow);
                    }
                    else if (currentRow > 0){
                        index = currUI.getModelIndexWithWrapping(cols - 1, currentRow - 1);
                    }
                    else {
                        index = size - 1;
                    }
                }
                else if(name.equals("selectNextRow")) {
                    if (currentRow < rows - 1) {
                        index = currUI.getModelIndexWithWrapping(currentCol, currentRow + 1);
                    }
                    else if (currentCol < cols - 1){
                        index = currUI.getModelIndexWithWrapping(currentCol + 1, 0);
                    }
                    else {
                        index = 0;
                    }
                }
                else if (name.equals("selectNextColumn")) {
                    if (currentCol < cols - 1) {
                        index = currUI.getModelIndexWithWrapping(currentCol + 1, currentRow);
                    }
                    else if (currentRow < rows - 1){
                        index = currUI.getModelIndexWithWrapping(0, currentRow + 1);
                    }
                    else {
                        index = 0;
                    }
                }
                else {
                    // some other event which we were not expecting
                    UsageException errorVar = new UsageException("Cannot handle action with name='"+name+"'");
                    ErrorMgr.addError(errorVar);
                    throw errorVar;
                }
                list.setSelectedIndex(index);
            }
            else {
                // some other kind of list
                originalAction.actionPerformed(e);
            }
        }
    }

    /**
     * Recalculate the number of columns based on the underlying radio list, then flag this component as requiring a layout
     */
    protected void recalculateSizes() {
        updateLayoutStateNeeded |= layoutOrientationChanged;
        switch (radioList.orientation) {
            case Constants.FO_HORIZONTAL:
                // The number of columns is the elements / wrap size, rounded up.
                this.columnCount = (radioList.getModel().getSize() + radioList.wrapSize - 1) / radioList.wrapSize;
                break;

            default:
                // The number of columns is just the wrap size
                this.columnCount = radioList.wrapSize;
                break;
        }
        this.recalculateColumnWidths();
    }

    /**
     * Listener class to handle when the wrapWidth and other properties that affect this UI change
     */
    protected class RadioListUIPropertyHandler implements PropertyChangeListener {
        public static final String wrapChangeEvent = "wrapSize";
        public static final String orientationChangeEvent = "orientation";
        public static final String elementListChangeEvent = "elementList";
        public static final String layoutPolicyChangeEvent = "layoutPolicy";
        public static final String messageSetChangeEvent = "textValueSetNum";

        // TF:02/07/2008:Added a listener for the message set change since this can
        // resize the labels and force a re-layout
        public void propertyChange(PropertyChangeEvent evt) {
            if (evt.getPropertyName().equals(wrapChangeEvent) ||
                    evt.getPropertyName().equals(orientationChangeEvent) ||
                    evt.getPropertyName().equals(layoutPolicyChangeEvent) ||
                    evt.getPropertyName().equals(elementListChangeEvent) ||
                    evt.getPropertyName().equals(messageSetChangeEvent)) {

                // Recalculate the rows and columns based on the layout policy and the wrap size
                recalculateSizes();
            }
        }
    }

    /**
     * This class is a renderer for radio buttons inside a radio list.
     * @author Tim
     *
     */
    public class RadioListCellRenderer extends JRadioButton implements ListCellRenderer {
        private static final long  serialVersionUID  = -4109555056306919652L;
        protected ListElement le = null;
        protected RadioList radioList;
        public RadioListCellRenderer(RadioList radioList){
            super();
            this.radioList = radioList;

            // Get advised when the owner's font has changed and reflect that in our font and layout
            this.radioList.addPropertyChangeListener("font", new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent evt) {
                    RadioListCellRenderer.this.setFont(RadioListCellRenderer.this.radioList.getFont());
                    recalculateSizes();
                }
            });

            // Inherit our parent's background colour
            this.setBackground(null);
            this.setFocusPainted(true);
          // TF:15/12/2009:DET-141:Set this up to allow inheriting of popup menu
          this.setInheritsPopupMenu(true);
        }

        public RadioListCellRenderer(RadioList radioList, ListElement pLe){
            this(radioList);
            this.le = pLe;
        }

        private boolean hasFocus = false;

        /**
         * If this cell has the focus, we need to set the highlight around it, and to do this we need to set the focus
         */
        @Override
        public boolean hasFocus() {
            return hasFocus;
        }

        // TF:1/8/07: If we're being removed from our parent, we never have focus. This fixes an issue where a radio list
        // with one element being used as a cell renderer in an array field did not tab properly.
        @Override
        public void removeNotify() {
            hasFocus = false;
            super.removeNotify();
        }

        /**
         * Given a value (hopefully a ListElement) return the string that should be
         * used to render that element.
         * @param value
         * @return
         */
        private String valueToString(Object value) {
            // TF:02/07/2008:Use the message catalog to get the text if it exists
          // TF:04/07/2008:Removed this code as we now set the textvalue of the elements in the list
          // upon setting the message catalog.
          String result = value.toString();
//          if (value instanceof ListElement && ((ListElement)value).getTextValueMsgNum() > 0) {
//            int setNumber = RadioListUI.this.radioList.getTextValueSetNum();
//            if (setNumber == 0) {
//              // This should default back to the Window set number
//              setNumber = UIutils.getDefaultMessageSet(radioList);
//              if (setNumber <= 0) {
//                return result;
//              }
//            }
//            int msgNumber = ((ListElement)value).getTextValueMsgNum();
//            result = MsgCatalog.getInstance().getString(setNumber, msgNumber);
//          }
          return result;
        }
       
        /**
         * Get the cell renderer for this component. In this case, it's just the current component which is a radio button
         * subclass, on which we override all the properties necessary.
         */
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            // TF:17/9/07:Check to see if there's a mnemonic on the radio button and set it if there is. Note that you can't actually
            // use this for an accelerator key in Java -- but it didn't work as an accelerator key in Forte either! Not sure why you'ld
            // actually want to do this, but some people seem to want to.
            this.setDisplayedMnemonicIndex(-1);
           
            // TF:02/07/2008:Changed to use standard mechanism for getting the value
            String strValue = valueToString(value);
           
            int offset = strValue.indexOf('&');
            int mnemonicIndex = -1;
           
            // CraigM:21/08/2008 - Corrected search for && and &
            while (offset >= 0) {
              // Check for &&
              if (offset < strValue.length() - 1 && strValue.charAt(offset+1) == '&') {
                strValue = strValue.substring(0, offset) + strValue.substring(offset+1); // Remove one &
              }
              else {
                mnemonicIndex = offset; // Single &, so this must be the mnemonic
                strValue = strValue.substring(0, offset) + strValue.substring(offset+1); // Remove the &
              }
              offset = strValue.indexOf('&', offset+1);
            }
            this.setText(strValue);
            this.setSelected(isSelected);
           
            if (mnemonicIndex != -1) {
              this.setDisplayedMnemonicIndex(mnemonicIndex);
            }
           
            // Make sure the highlight cell has a dashed rectangle
            hasFocus = cellHasFocus ||
              (radioList.isFocusOwner() && radioList.getSelectedIndex() == -1 &&
                  (radioList.getLastSelectedIndex() == index ||
                      (radioList.getLastSelectedIndex() == -1 && index == 0)));
           
            // Makes the radio buttons look disabled (gray out) when they are disabled
            this.setEnabled(list.isEnabled());
            return this;
        }


        /**
         * Get the preferred size of this component. Note that this isn't quite the normal one -- we make them a little closer together
         * to make them look more like the forte one.
         * @param value
         * @return
         */
        public Dimension getPreferredSize(Object value) {
            Font f = this.radioList.getFont();
            FontMetrics fm = this.radioList.getFontMetrics(f);
            // TF:02/07/2008:Changed to use standard mechanism for getting the value
            this.setText(valueToString(value));
            int width = this.getPreferredSize().width;
            int height = (fm.getHeight() + fm.getDescent()) + 1;
            Dimension d = new Dimension(width, height);
            return d;
        }

        /**
         * Paint the radio button. If we're using a windows look and feel, the mnemonic on the radio button is hidden by default,
         * so we can't view it even if we set up the mnemonic properly. So, on this platform, we override whether it's hidden or
         * not and reset it later (so it doesn't affect other buttons). This makes it look the same as forte.
         */
        @Override
        public void paint(Graphics g) {
            if (UIManager.getLookAndFeel() instanceof WindowsLookAndFeel) {
                boolean oldValue = WindowsLookAndFeel.isMnemonicHidden();
                WindowsLookAndFeel.setMnemonicHidden(false);
                super.paint(g);
                WindowsLookAndFeel.setMnemonicHidden(oldValue);
            }
            else {
                super.paint(g);
            }
        }
    }
}
TOP

Related Classes of DisplayProject.RadioListUI

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.