package org.limewire.ui.swing.components;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.DefaultCellEditor;
import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;

import org.limewire.collection.SortedList;
import org.limewire.ui.swing.util.FontUtils;
import org.limewire.ui.swing.util.I18n;



/**
 * Provides a list of checkboxes in the look and feel of a {@link JList}.
 * <p>
 * The seleted items can be retrieved easily using {@link #getSelectedObjects()}
 * or {@link #getSelectedElements(Object[])}.
 */
public class CheckBoxList<E> extends BoxPanel {

    // lower case since hyperlink
    private static final String REMOVE_TEXT = I18n.tr("remove");
    
    private String disabledTooltip = null;
    
    private boolean areAllRemoveable = false;
    
    private boolean selectOff;
    private JScrollPane scrollPane;
    private JTable checkBoxList;
    private CheckBoxListSelectionListener selectionListener;
    private CheckBoxListCheckChangeListener checkListener;
    private Object parent;
   
    /**
     * The set of elements included on the panel.
     */
    private final Set<E>     elements  = new HashSet<E>();
    
    /**
     * The subsets of unchecked and checked elements on the panel.
     */
    private final Set<E>     unchecked = new HashSet<E>();
    private final Set<E>     checked   = new HashSet<E>();
    
    /**
     * The set of singly removable items
     */
    private final Set<E>     removableElements = new HashSet<E>();
    
    /**
     * The subset of individual items that have been force disabled.
     */
    private final Set<E>     disabled  = new HashSet<E>();
    
    /**
     * The subset of items that are bolded for notification in the panel.
     */
    private final Set<E>     bolded    = new HashSet<E>();
   
        
    private Object selected;
    
    public static final int SELECT_FIRST_ON  = 0;
    public static final int SELECT_FIRST_OFF = 1;
    private static final int DEFAULT_ROW_HEIGHT = 22;
    
    /**
     * Holds the text provider used for this instance, is guaranteed to be 
     * non-null.
     */
    private TextProvider<E> provider;
    
    /**
     * Holds the provider used to indicate if an element should have a divider
     *  drawn above.  Null if the feature is disabled. 
     */
    private ExtrasProvider<E> extrasProvider;
    
    /**
     * Holds the array of checkboxes that are displayed.
     */
    private List<E> items; 
    /**
     * Holds the visible row count if set.
     */
    private int visibleRowCount = -1;
    /**
     * The key for the text provider property, when the text provider changes
     * for this component a property change event is fired with this key.
     */
    public static final String TEXT_PROVIDER_PROPERTY = "textProvider";

    private CheckBoxCellEditor editor;

    private boolean checkBoxesVisible = true;

   
    /**
     * Constructs a checkbox list for a collection of elements.
     * @param elements the collection of objects to be displayed in the checkbox list
     * @param provider the provider that retrieves the textual display information 
     * from the objects
     * @throws NullPointerException if the text provider is <code>null</code>
     */
    public CheckBoxList(Collection<E> elements, Collection<E> notCheckedElements, TextProvider<E> provider, ExtrasProvider<E> separatorProvider, int mode) {
        this.provider = provider;
        this.extrasProvider = separatorProvider;
        
        if (provider == null) {
            throw new NullPointerException("provider must not be null");
        }

        this.parent = this;
        this.selectionListener = null;
        
        this.selectOff = (mode == SELECT_FIRST_OFF);

        this.initialize();
        this.setElements(elements, notCheckedElements);
       
    }
    
    public CheckBoxList(Collection<E> elements, Collection<E> notCheckedElements, TextProvider<E> provider, int mode) {
        this(elements, notCheckedElements, provider, null, mode);
    }
    
    
    public CheckBoxList(Collection<E> elements, TextProvider<E> provider, boolean checkAll, int mode) {
        this(elements, checkAll ? new HashSet<E>() : elements, provider, null, mode);
    }

    
    public CheckBoxList(Collection<E> totalElements, Collection<E> notCheckedElements, int mode) {
        this(totalElements, notCheckedElements, new DefaultTextProvider<E>(), null, mode);
    }    
    
   public CheckBoxList(Collection<E> elements, TextProvider<E> provider, int mode) {
        this(elements, new HashSet<E>(), provider, null, mode);
    }
    
    
    public CheckBoxList(Collection<E> elements) {
        this(elements, new DefaultTextProvider<E>(), SELECT_FIRST_ON);
    }
    
    public CheckBoxList(Collection<E> elements, TextProvider<E> provider) {
        this(elements, provider, SELECT_FIRST_ON);
    }
    
    /**
     * Convenience wrapper for {@link #setElements(Object[], boolean)
     * setObjects(Object[], true)}.
     */
    public void setElements(Collection<E> elements) {
        setElements(elements, true);
    }
  
    /**
     * Wrapper for setElements.
     */
    public void setElements(Collection<E> elements, boolean checked) {
        if (checked) {
            this.setElements(elements, new HashSet<E>());
        } 
        else {
            this.setElements(elements, elements);
        }
    }
    
    /**
     * Sets the element list.
     */
    public void setElements(Collection<E> elements, Collection<E> notCheckedElements) {
        this.items = new SortedList<E>(elements);
        
        Object[][] rowData = new Object[elements.size()][1];        
        for ( int i=0 ; i<elements.size() ; i++ )
            rowData[i][0] = items.get(i);
       
        
        this.elements.clear();
        this.checked.clear();
        this.unchecked.clear();
                
        this.elements.addAll(elements);
        this.checked.addAll(elements);
        this.checked.removeAll(notCheckedElements);
        this.unchecked.addAll(notCheckedElements);
        
        if (rowData.length > 0) {
            this.selected = rowData[0];
        }
        
        this.checkBoxList.setModel(new DefaultTableModel(rowData, new String[]{""}));        
    }
    
    
    /**
     * Force updates every item in the list.
     */
    public void update() {
        ((DefaultTableModel)this.checkBoxList.getModel()).fireTableStructureChanged();
    }
     
    /**
     * Deletes an item from the list with key "key".
     */
    public void removeItem(E key) {
        Set<E> newElements = new HashSet<E>(this.elements);
        Set<E> newUnchecked = new HashSet<E>(this.unchecked);
        newElements.remove(key);
        newUnchecked.remove(key);
        this.setElements(newElements, newUnchecked);
        removableElements.remove(key);
    }
    
    public void addItem(E key) {
        Set<E> newElements = new HashSet<E>(this.elements);
        Set<E> newUnchecked = new HashSet<E>(this.unchecked);
        newElements.add(key);
        newUnchecked.add(key);
        this.setElements(newElements, newUnchecked);
    }
   
    /**
     * Enables or disables the delete buttons on each row.
     */
    public void setAllRemovable(boolean state) {
        this.areAllRemoveable = state;
        this.update();
    }
    
    /**
     * Sets a specific element as removable or not.
     */
    public void setRemovable(E key, boolean state) {
        if (state) {
            removableElements.add(key);
        }
        else {
            removableElements.remove(key);
        }
        
        update();
    }
    
     /**
     * Sets the enabled state of the List and its members.
     */
    @Override
    public void setEnabled(boolean enabled) {
        this.checkBoxList.setEnabled(enabled);
    }
    
    public void setCheckBoxesVisible(boolean visible) {
        this.checkBoxesVisible = visible;
        this.update();
        
    }
    
    /**
     * Sets the custom tooltip for disabled items.
     */
    public void setDisabledTooltip(String tooltip) {
        this.disabledTooltip = tooltip;        
    }
    
    /**
     * Gets the custom tooltip for disabled items.
     */
    public String getDisabledTooltip() {
        return this.disabledTooltip;        
    }
    
    private static void ensureRowVisible(JTable table, int row) {
        if(row != -1) {
            Rectangle cellRect = table.getCellRect(row, 0, false);
            Rectangle visibleRect = table.getVisibleRect();
            if( !visibleRect.intersects(cellRect) )
                table.scrollRectToVisible(cellRect);
        }
    }
    
    /**
     * Ensures the row with the key is visible.
     */
    public void ensureRowVisible(E key) {
        if (key == null) {
            throw new IllegalArgumentException();
        }
        
        int row = 0;   
               
        for ( ; row<this.checkBoxList.getModel().getRowCount() ; row++ ) {
            Object item = this.checkBoxList.getModel().getValueAt(row, 0);
            if (item == null) {
                return;
            }
            
            if (item.equals(key)) {
                break;
            }
        }
                        
        ensureRowVisible(this.checkBoxList, row);
    }
    
    /**
     * Selects an item in the list.
     */
    public void setItemSelected(E key) {
        if (key == null) {
            throw new IllegalArgumentException();
        }
        
        this.selected = key;
        this.ensureRowVisible(key);
        this.editor.notifyChange();
        this.update();
    }
    
    /**
     *  Checks an item, returns true if the item has changed state
     *  otherwise false.
     */
    public boolean setItemChecked(E key) {
        if (key == null) {
            throw new IllegalArgumentException();
        }
        
        if (!this.checked.contains(key)) {
            this.checked.add(key);
            this.unchecked.remove(key);
        }

        
        this.bolded.add(key);
        this.update();
        this.ensureRowVisible(key);
        
        (new Timer(2000, new BoldRemoveListener(key))).start();

        return true;
    }

    
    private class BoldRemoveListener implements ActionListener {
        private E key;
        
        public BoldRemoveListener(E key) {
            this.key = key;
        }
        
        @Override
		public void actionPerformed(ActionEvent e) {
            bolded.remove(key);
            update();
        }
    }
    
    
    /** 
     * Disable or enable a set of items.
     */
    public void setItemsEnabled(Set<E> keys, boolean state) {
        if (state) {
            Set<E> toUncheck = new HashSet<E>(this.checked);
            toUncheck.retainAll(keys);
            toUncheck.retainAll(this.disabled);
            
            this.checked.removeAll(toUncheck);
            this.unchecked.addAll(toUncheck);
            
            
            this.disabled.removeAll(keys);
        }
        else {
            this.disabled.addAll(keys);
        }
        this.update();
    }
    
    /** 
     * Check or uncheck a set of items.
     */
    public void setItemsChecked(Set<E> keys, boolean state) {
        if (state) {
            this.unchecked.removeAll(keys);
            this.checked.addAll(keys);
        }
        else {
            this.unchecked.addAll(keys);
            this.checked.removeAll(keys);
        }
        this.update();
    }
    

    /**
     * Returns the enabled state of the List and its members.
     */
    @Override
    public boolean isEnabled() {
        return this.checkBoxList.isEnabled();
    }
    
    /**
     * Sets the preferred number of visible rows. 
     */
    public void setVisibleRowCount(int rows) {
        this.visibleRowCount = rows;
        this.setPreferredSize(this.getPreferredScrollableViewportSize());
    }
    
        
    public Dimension getPreferredScrollableViewportSize() {
        return (visibleRowCount > 0 && items.size()> 0) ?
                new Dimension(getPreferredSize().width, visibleRowCount * this.checkBoxList.getRowHeight())
                : getPreferredSize();
    }
    
    
    public void setSelectionListener(CheckBoxListSelectionListener listener) {
        this.selectionListener = listener;  
    }
    
    public void setCheckChangeListener(CheckBoxListCheckChangeListener listener) {
        this.checkListener = listener;  
    }

    /**
     * Sets a new next provider and fires a property change event.
     * @param provider the new text provider
     * @throws NullPointerException if the new provider is <code>null</code>
     */
    public void setTextProvider(TextProvider<E> provider) {
        if (provider == null) {
            throw new NullPointerException("provider must not be null");
        }
        TextProvider oldProvider = this.provider;
        this.provider = provider;
        firePropertyChange(TEXT_PROVIDER_PROPERTY, oldProvider, this.provider);
    }
    
    /**
     * Returns the typed array of selected objects taking
     *  enable/disable into account.
     */
    public List<E> getCheckedElements() {
        if (this.checkBoxList.isEnabled() && this.checked.size() > 0) {
            Set<E> totalChecked = new HashSet<E>(checked);
            totalChecked.removeAll(this.disabled);
            return new LinkedList<E>(totalChecked);
        }
        else {
            return Collections.emptyList();
        }
    }

    
    /**
     * Returns the typed array of unselected objects taking
     *  enable/disable into account.
     */
    public List<E> getUncheckedElements() {
        if (!this.checkBoxList.isEnabled()) {
            return new LinkedList<E>(this.elements);
        }
        
        Set<E> toUncheck = new HashSet<E>(this.checked);
        toUncheck.retainAll(this.disabled);
        
        if (this.unchecked.size() > 0) {
            Set<E> totalUnchecked = new HashSet<E>(this.unchecked);
            totalUnchecked.addAll(toUncheck);

            return new LinkedList<E>(totalUnchecked);
        }
        else {
            return new LinkedList<E>(toUncheck);
        }
    }

    
    /**
     * Returns a new list of the total elements contained.
     */
    public Set<E> getElements() {
        return new HashSet<E>(this.elements);
    }
    
    /**
     * Returns a new list of the unchecked elements
     *  without regards to the enabled/disabled status
     *  of the component or elements.
     */
    public Set<E> getRawUncheckedElementsAsSet() {
        return new HashSet<E>(this.unchecked);
    }
    
    
    /**
     * Returns an array of actions operation on the checkbox list.
     * <p>
     * The first action sets all items to selected, the second deselects all
     * items.
     */
    public Action[] getActions() {
        return new Action[] { new SelectAllAction(), new DeselectAllAction() };
    }
    

    
    
    private void initialize()   {
        this.setBorder(new EmptyBorder(0, 0, 0, 0));
                
        this.editor = new CheckBoxCellEditor();
        
        this.checkBoxList = new JTable();    
        this.checkBoxList.setDefaultRenderer(Object.class, new CheckBoxCellRenderer());
        this.checkBoxList.setDefaultEditor(Object.class, editor);
        this.checkBoxList.setRowHeight(DEFAULT_ROW_HEIGHT);
        
        // NOTE: workaround for windows/linux swing header consistency showing problem
        this.checkBoxList.setTableHeader(new JTableHeader());
        this.checkBoxList.getTableHeader().setVisible(false);
        this.checkBoxList.getTableHeader().setSize(0, 0);

        this.scrollPane = new JScrollPane(checkBoxList);
        this.add(scrollPane);
        
        this.updateUI();
    }    
    
    
    @Override
    public void updateUI() {
        super.updateUI();

        if (this.checkBoxList == null) {
            return;
        }
        
        this.setBackground(UIManager.getColor("List.textBackground"));
        this.checkBoxList.setBackground(UIManager.getColor("List.background"));
        this.checkBoxList.setGridColor(UIManager.getColor("List.background"));
        this.scrollPane.getViewport().setBackground(UIManager.getColor("List.background"));
    }

    

    /**
     * Provides a default implementation of the {@link TextProvider} interface.
     */
    public static class DefaultTextProvider<E> implements TextProvider<E> {

        /**
         * Returns {@link Object#toString() obj.toString()}.
         */
        @Override
		public String getText(E obj) {
            return obj.toString();
        }

        /**
         * Returns <code>null</code>.
         */
        @Override
		public String getToolTipText(E obj) {
            return null;
        }

        /**
         * Returns <code>null</code>.
         */
        @Override
		public Icon getIcon(E obj) {
            return null;
        }
        
    }
    
    
    
    
    
    private class SelectAllAction extends AbstractAction {

        public SelectAllAction() {
            putValue(Action.NAME, I18n.tr
                    ("Select All"));
            putValue(Action.SHORT_DESCRIPTION, I18n.tr
                    ("Marks all Items as Selected"));
        }
        
        @Override
		public void actionPerformed(ActionEvent e) {
            checked.addAll(unchecked);
            unchecked.clear();


            scrollPane.setVisible(false);
            scrollPane.setVisible(true);
        }
        
    }
    
    private class DeselectAllAction extends AbstractAction {

        public DeselectAllAction() {
            putValue(Action.NAME, I18n.tr
                    ("Deselect All"));
            putValue(Action.SHORT_DESCRIPTION, I18n.tr
                    ("Deselects all Items in the List"));
        }
        
        @Override
		public void actionPerformed(ActionEvent e) {
            unchecked.addAll(checked);
            checked.clear();
            
            scrollPane.setVisible(false);
            scrollPane.setVisible(true);
        }
        
    }

     /**
     * Check box tree cell renderer.
     */
    private class CheckBoxCellRenderer extends DefaultTableCellRenderer {
        
        private static final long serialVersionUID = -8209871264709364379L;
        
        private IconDataCheckBox checkBox = new IconDataCheckBox();
                
        @Override
        @SuppressWarnings("unchecked")
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, 
                int row, int column) {
            super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);

            // See LWC-1158 for null weirdness
            if(value != null)
                checkBox.setData((E)value);
                        
            if (!selectOff && value != null && value.equals(selected)) {
                this.checkBox.setBackground(UIManager.getColor("List.selectionBackground"));
                this.checkBox.setForeground(UIManager.getColor("List.selectionForeground"));
            }
            else {
                this.checkBox.setBackground(UIManager.getColor("List.textBackground"));
                this.checkBox.setForeground(UIManager.getColor("List.textForeground"));
            }
            
            if (bolded.contains(value)) {
                this.checkBox.setBold(true);
            } 
            else {
                this.checkBox.setBold(false);
            }
            
            if (disabled.contains(value)) {
                if (disabledTooltip != null) {
                    checkBox.setToolTipText(disabledTooltip);
                }
                checkBox.setEnabled(false);
                checkBox.setSelected(false);
            }
            else {
                checkBox.setSelected(!unchecked.contains(value));
                checkBox.setEnabled(table.isEnabled());
            }
            
            return checkBox;
        }
    }
    

    /**
     * Checkbox tree cell editor.
     */
    private class CheckBoxCellEditor extends DefaultCellEditor {
                       
        private CustomEditorDelegate customDelegate;

        public CheckBoxCellEditor() {
            super(new JCheckBox());
                        
            IconDataCheckBox checkBox = new IconDataCheckBox();
            editorComponent = checkBox;
          
            checkBox.setRequestFocusEnabled(false);
            
            this.customDelegate = new CustomEditorDelegate();
            this.delegate = customDelegate;            
            checkBox.addActionListener(customDelegate);
            checkBox.addTopMouseListener(customDelegate);
        
           
        }
        
        /**
         * Used to pass notifications of selection focus changes down to the
         *  table editor classes so selection changes can be handled 
         *  correctly.
         */
        public void notifyChange() {
            this.customDelegate.notifyChange();
        }
        
        /**
         * Overridden to pass the value to the delegate and not just its
         * string representation as done in the super class.
         */
        @Override
        public Component getTreeCellEditorComponent(JTree tree, Object value,
                boolean isSelected, boolean expanded, boolean leaf, int row) {
            
            delegate.setValue(value);
            return editorComponent;
        }
        
        /**
         * The custom table editor model to implement focus selection and all the checkbox
         *  update control.
         */
        private class CustomEditorDelegate extends EditorDelegate implements MouseListener {
            
            private static final long serialVersionUID = -7007164079207676831L;
            private Object lastValue;
            
            public CustomEditorDelegate() {
                this.lastValue = this.value;
            }
            
            
            public void notifyChange() {
                this.lastValue = null;
            }
            
            @SuppressWarnings("unchecked")
            @Override
            public void setValue(Object value) { 

                if (value == null) {
                    return;
                }
                
                IconDataCheckBox box = ((IconDataCheckBox)editorComponent);
                
                box.setData((E)value);   
                box.setEnabled(isEnabled());
                
                if (disabled.contains(value)) {
                    if (disabledTooltip != null) {
                        box.setToolTipText(disabledTooltip);
                    }
                    box.setEnabled(false);
                    box.setSelected(false);
                    
                    this.value = value;
                    return;
                }
                
                boolean isSelected;
                
                selected = value;
                if (this.lastValue == value) {
                    isSelected = true;
                }
                else {
                    isSelected = false;
                    repaint();
                    if (selectionListener != null) {   
                        selectionListener.valueChanged(new CheckBoxListSelectionEvent(parent, value));
                    }
                }
                
                
                if (checkBoxesVisible) {
                
                    if (selectOff || isSelected) {

                        boolean decheck = unchecked.contains(value);

                        if (decheck) {
                            unchecked.remove(value);
                            checked.add((E)value);
                        }
                        else {
                            unchecked.add((E)value);
                            checked.remove(value);
                        }

                        if (checkListener != null) {
                            checkListener.valueChanged(new CheckBoxListCheckChangeEvent(parent, value, decheck));
                        }
                    }
                }

                box.setSelected(checked.contains(value));
                                    
                this.lastValue = value;
                this.value = value;
            }
            
            @Override
            public Object getCellEditorValue() {
                return this.value;
            }
            
            
            /**
             * Overridden to allow disable selection so the custom
             *  selection model can be used.
             */
            @Override
            public boolean shouldSelectCell(EventObject anEvent) {
                return false;
            }

            @Override
			public void mouseClicked(MouseEvent e) {
            }

            @Override
			public void mouseEntered(MouseEvent e) {
            }

            @Override
			public void mouseExited(MouseEvent e) {
            }

            @Override
			public void mousePressed(MouseEvent e) {
                this.stopCellEditing();
            }

            @Override
			public void mouseReleased(MouseEvent e) {
            }
        
    }
    

  
        

    }

    
    /**
     * Stores selection change events.
     */
    public static class CheckBoxListSelectionEvent extends EventObject{
        private Object selected;
       
        public CheckBoxListSelectionEvent(Object source, Object selected) {
            super(source);
            this.selected = selected;
        }
        
        /**
         * Gets the item that has just been selected.
         * @return selected item
         */
        public Object getSelected() {
            return this.selected;
        }
    }


    /**
     * Stores check/uncheck events .
     */
    public static class CheckBoxListCheckChangeEvent<E> extends EventObject{
        private E selected;
        private boolean checked;
       
        public CheckBoxListCheckChangeEvent(Object source, E selected, boolean checked) {
            super(source);
            this.selected = selected;
            this.checked = checked;
        }
        
        /**
         * Gets the item that was changed.
         * @return The item changed
         */
        public E getSelected() {
            return this.selected;
        }
        
        /**
         * Gets the check state of the change.
         * @return the check state of the change
         */
        public boolean getChecked() {
            return this.checked;
        }
    }

    /**
     * Defines the listener to notify item a change in list selection
     *  if select first mode is enabled.
     */
    public static interface CheckBoxListSelectionListener {
        public void valueChanged(CheckBoxListSelectionEvent e);        
    }
        
    /**
     * Defines the listener to notify item check/uncheck
     *  events in the list.
     */
    public static interface CheckBoxListCheckChangeListener<E> {
        public void valueChanged(CheckBoxListCheckChangeEvent<E> e);        
    }
    
    /** 
     * This interface is used to define a provider with a
     *  check if an element requires is required to have a horizontal
     *  separator drawn above it. 
     */
    public interface ExtrasProvider<E> {
        boolean isSeparated(E obj);
        String getComment(E obj);
        int getCommentFieldSize(); 
    }
    
    /**
     * This interface defines the requirements for a kind of renderer class which
     * retrieves the display information for the given object. 
     */
    public interface TextProvider<E> {
        /**
         * Returns the label text displayed next to the checkbox. 
         * @param obj the underlying data object
         */
        String getText(E obj);
        /**
         * Returns the tooltip text for the checkbox item.
         * @param obj the underlying data object
         */
        String getToolTipText(E obj);
        /**
         * Returns an icon that is displayed to the left of the checkbox.
         */
        Icon getIcon(E obj);
    }
    
    
    /**
     *  Composite class to make a check box with an icon along with the text.
     *      
     */
    private class IconDataCheckBox extends BoxPanel {
        
        private E obj;
        private JCheckBox checkBox;
        private JLabel label;
        private JLabel commentlabel;
        private DeleteButton button;
        private Border blankBorder;
        private SeperatorBorder sepBorder;
        private Font originalFont;
        private Font boldFont;

        public IconDataCheckBox() {
            super(BoxPanel.X_AXIS);
            
            sepBorder = new SeperatorBorder();
            blankBorder = BorderFactory.createMatteBorder(4,4,4,0, (Icon)null);

            label = new JLabel();
            label.setBorder(blankBorder);
            
            commentlabel = new JLabel();
            commentlabel.setBorder(BorderFactory.createEmptyBorder(4,0,4,0));
            
            checkBox = new JCheckBox("", true);
            checkBox.setVisible(checkBoxesVisible);
            
            button = new DeleteButton();
                  
            add(Box.createHorizontalStrut(4));
            add(checkBox);
            add(label);
            add(commentlabel);
            add(Box.createHorizontalStrut(1));
            add(Box.createHorizontalGlue());
            add(button);
            
            updateLook();
   
            originalFont = label.getFont();
            boldFont = originalFont.deriveFont(originalFont.getStyle() | Font.BOLD | Font.ITALIC);
            
            commentlabel.setFont(this.originalFont);
        }
        
//        public E getData() {
//            return obj;
//        }
        
        public void setRemovable(boolean state) {
            this.button.setVisible(state);
        }
        
        public void setData(E obj) {
            this.obj = obj;
            
            checkBox.setVisible(checkBoxesVisible);
            this.setRemovable(areAllRemoveable || removableElements.contains(obj));
            
            String text = provider.getText(obj);

            label.setIcon(provider.getIcon(obj));
            
            if (extrasProvider != null) {
                
                label.setText(text + " ");

                
                Rectangle2D textSize = FontUtils.getLongestTextArea(this.originalFont, text);
                
                if (extrasProvider.getCommentFieldSize() > textSize.getWidth()+26) {
                    label.setPreferredSize(new Dimension(extrasProvider.getCommentFieldSize(), 18));
                    label.setSize(label.getPreferredSize());
                    label.setMinimumSize(label.getPreferredSize());
                    label.setMaximumSize(label.getPreferredSize());
                }
                
                commentlabel.setText(extrasProvider.getComment(obj));
                
                if (extrasProvider.isSeparated(obj)) {
                    label.setBorder(sepBorder);
                }
                else {
                    label.setBorder(blankBorder);
                }
            }
            else {
                label.setText(text);
            }
            
            this.setToolTipText(provider.getToolTipText(obj));
        }
       
        /**
         * Returns whether or not the check box is checked or not.
         */
//        public boolean isSelected() {
//            return checkBox.isSelected();
//        }
        
        public void setSelected(boolean selected) {
            checkBox.setSelected(selected);
        }
        

        @Override
        public void setEnabled(boolean enabled) {
            checkBox.setEnabled(enabled);
            label.setEnabled(enabled);
        }
        
        
        @Override
        public void setForeground(Color c) {
            if (label != null) {
                label.setForeground(c);
            }
        }
        
        public void setBold(boolean b) {
            if (b) {
                label.setFont(boldFont);
            } 
            else {
                label.setFont(originalFont);
            }
        }
        
        private void updateLook() {
            setOpaque(true);
            setForeground(UIManager.getColor("List.foreground"));
            setBackground(UIManager.getColor("List.background"));
            
            checkBox.setBackground(UIManager.getColor("List.background"));
            checkBox.setForeground(UIManager.getColor("List.foreground"));
            checkBox.setFont(UIManager.getFont("Table.font"));
            checkBox.setOpaque(false);
            
            label.setBackground(UIManager.getColor("List.background"));
            label.setForeground(UIManager.getColor("List.foreground"));
            label.setFont(UIManager.getFont("Table.font"));
            label.setOpaque(false);
       }
        
        /**
         * Adds an <code>ActionListener</code> to the button.
         * @param l the <code>ActionListener</code> to be added
         */
        public void addActionListener(ActionListener l) {
            this.checkBox.addActionListener(l);
        }
        
        
        public void addTopMouseListener(MouseListener l) {
            this.addMouseListener(l);
            this.label.addMouseListener(l);
            this.commentlabel.addMouseListener(l);
        }
        
        
        
        private class DeleteButton extends HyperlinkButton {
            
            public DeleteButton() {
                
                setFont(new Font("Dialog", Font.PLAIN, 11));
                setText(REMOVE_TEXT);
                setBorder(BorderFactory.createEmptyBorder(0,0,0,10));
                
                this.setVisible(false);
                this.addMouseListener(new MouseListener() {
                    @Override
					public void mouseClicked(MouseEvent e) {
                    }
                    @Override
					public void mouseEntered(MouseEvent e) {
                    }
                    @Override
					public void mouseExited(MouseEvent e) {
                    }
                    @Override
					public void mousePressed(MouseEvent e) {
                        removeItem(obj);                     
                    }
                    @Override
					public void mouseReleased(MouseEvent e) {
                    }
                });
            }
        }
        
    }
    
    

}
