001/*
002 * $Id: DraggableTabbedPane.java,v 1.32 2011/03/24 18:13:11 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 * 
012 * All Rights Reserved
013 * 
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.  
016 * 
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 * 
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025 * GNU Lesser Public License for more details.
026 * 
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program.  If not, see http://www.gnu.org/licenses.
029 */
030
031package edu.wisc.ssec.mcidasv.ui;
032
033import java.awt.Color;
034import java.awt.Component;
035import java.awt.Cursor;
036import java.awt.FontMetrics;
037import java.awt.Graphics;
038import java.awt.Image;
039import java.awt.Point;
040import java.awt.Rectangle;
041import java.awt.datatransfer.DataFlavor;
042import java.awt.datatransfer.Transferable;
043import java.awt.dnd.DnDConstants;
044import java.awt.dnd.DragGestureEvent;
045import java.awt.dnd.DragGestureListener;
046import java.awt.dnd.DragSource;
047import java.awt.dnd.DragSourceDragEvent;
048import java.awt.dnd.DragSourceDropEvent;
049import java.awt.dnd.DragSourceEvent;
050import java.awt.dnd.DragSourceListener;
051import java.awt.dnd.DropTarget;
052import java.awt.dnd.DropTargetDragEvent;
053import java.awt.dnd.DropTargetDropEvent;
054import java.awt.dnd.DropTargetEvent;
055import java.awt.dnd.DropTargetListener;
056import java.awt.event.InputEvent;
057import java.awt.event.MouseEvent;
058import java.awt.event.MouseListener;
059import java.awt.event.MouseMotionListener;
060import java.util.HashMap;
061import java.util.List;
062import java.util.Map;
063
064import javax.swing.Icon;
065import javax.swing.ImageIcon;
066import javax.swing.JComponent;
067import javax.swing.JTabbedPane;
068import javax.swing.SwingUtilities;
069import javax.swing.plaf.basic.BasicTabbedPaneUI;
070import javax.swing.plaf.metal.MetalTabbedPaneUI;
071
072import org.w3c.dom.Element;
073
074import ucar.unidata.idv.IntegratedDataViewer;
075import ucar.unidata.idv.ui.IdvWindow;
076import ucar.unidata.ui.ComponentGroup;
077import ucar.unidata.ui.ComponentHolder;
078import ucar.unidata.util.GuiUtils;
079import ucar.unidata.xml.XmlUtil;
080import edu.wisc.ssec.mcidasv.Constants;
081import edu.wisc.ssec.mcidasv.ui.DraggableTabbedPane.TabButton.ButtonState;
082
083/**
084 * This is a rather simplistic drag and drop enabled JTabbedPane. It allows
085 * users to use drag and drop to move tabs between windows and reorder tabs.
086 */
087public class DraggableTabbedPane extends JTabbedPane implements 
088    DragGestureListener, DragSourceListener, DropTargetListener, MouseListener,
089    MouseMotionListener
090{
091    private static final long serialVersionUID = -5710302260509445686L;
092
093    /** Local shorthand for the actions we're accepting. */
094    private static final int VALID_ACTION = DnDConstants.ACTION_COPY_OR_MOVE;
095
096    /** Path to the icon we'll use as an index indicator. */
097    private static final String IDX_ICON = 
098        "/edu/wisc/ssec/mcidasv/resources/icons/tabmenu/go-down.png";
099
100    /** 
101     * Used to signal across all DraggableTabbedPanes that the component 
102     * currently being dragged originated in another window. This'll let McV
103     * determine if it has to do a quiet ComponentHolder transfer.
104     */
105    protected static boolean outsideDrag = false;
106
107    /** The actual image that we'll use to display the index indications. */
108    private final Image INDICATOR = 
109        (new ImageIcon(getClass().getResource(IDX_ICON))).getImage();
110
111    /** The tab index where the drag started. */
112    private int sourceIndex = -1;
113
114    /** The tab index that the user is currently over. */
115    private int overIndex = -1;
116
117    /** Used for starting the dragging process. */
118    private DragSource dragSource;
119
120    /** Used for signaling that we'll accept drops (registers listeners). */
121    private DropTarget dropTarget;
122
123    /** The component group holding our components. */
124    private McvComponentGroup group;
125
126    /** The IDV window that contains this tabbed pane. */
127    private IdvWindow window;
128
129    /** Keep around this reference so that we can access the UI Manager. */
130    private IntegratedDataViewer idv;
131
132    /**
133     * Mostly just registers that this component should listen for drag and
134     * drop operations.
135     * 
136     * @param win The IDV window containing this tabbed pane.
137     * @param idv The main IDV instance.
138     * @param group The {@link McvComponentGroup} that holds this component's tabs.
139     */
140    public DraggableTabbedPane(IdvWindow win, IntegratedDataViewer idv, McvComponentGroup group) {
141        dropTarget = new DropTarget(this, this);
142        dragSource = new DragSource();
143        dragSource.createDefaultDragGestureRecognizer(this, VALID_ACTION, this);
144
145        this.group = group;
146        this.idv = idv;
147        window = win;
148
149        addMouseListener(this);
150        addMouseMotionListener(this);
151
152        if (getUI() instanceof MetalTabbedPaneUI) {
153            setUI(new CloseableMetalTabbedPaneUI(SwingUtilities.LEFT));
154            currentTabColor = indexColorMetal;
155        } else {
156            setUI(new CloseableTabbedPaneUI(SwingUtilities.LEFT));
157            currentTabColor = indexColorUglyTabs;
158        }
159    }
160
161    /**
162     * Triggered when the user does a (platform-dependent) drag initiating 
163     * gesture. Used to populate the things that the user is attempting to 
164     * drag. 
165     */
166    public void dragGestureRecognized(DragGestureEvent e) {
167        sourceIndex = getSelectedIndex();
168
169        // transferable allows us to store the current DraggableTabbedPane and
170        // the source index of the drag inside the various drag and drop event
171        // listeners.
172        Transferable transferable = new TransferableIndex(this, sourceIndex);
173
174        Cursor cursor = DragSource.DefaultMoveDrop;
175        if (e.getDragAction() != DnDConstants.ACTION_MOVE)
176            cursor = DragSource.DefaultCopyDrop;
177
178        dragSource.startDrag(e, cursor, transferable, this);
179    }
180
181    /** 
182     * Triggered when the user drags into <tt>dropTarget</tt>.
183     */
184    public void dragEnter(DropTargetDragEvent e) {
185        DataFlavor[] flave = e.getCurrentDataFlavors();
186        if ((flave.length == 0) || !(flave[0] instanceof DraggableTabFlavor))
187            return;
188
189        //System.out.print("entered window outsideDrag=" + outsideDrag + " sourceIndex=" + sourceIndex);
190
191        // if the DraggableTabbedPane associated with this drag isn't the 
192        // "current" DraggableTabbedPane we're dealing with a drag from another
193        // window and we need to make this DraggableTabbedPane aware of that.
194        if (((DraggableTabFlavor)flave[0]).getDragTab() != this) {
195            //System.out.println(" coming from outside!");
196            outsideDrag = true;
197        } else {
198            //System.out.println(" re-entered parent window");
199            outsideDrag = false;
200        }
201    }
202
203    /**
204     * Triggered when the user drags out of {@code dropTarget}.
205     */
206    public void dragExit(DropTargetEvent e) {
207        //      System.out.println("drag left a window outsideDrag=" + outsideDrag + " sourceIndex=" + sourceIndex);
208        overIndex = -1;
209
210        //outsideDrag = true;
211        repaint();
212    }
213
214    /**
215     * Triggered continually while the user is dragging over 
216     * {@code dropTarget}. McIDAS-V uses this to draw the index indicator.
217     * 
218     * @param e Information about the current state of the drag.
219     */
220    public void dragOver(DropTargetDragEvent e) {
221        //      System.out.println("dragOver outsideDrag=" + outsideDrag + " sourceIndex=" + sourceIndex);
222        if ((!outsideDrag) && (sourceIndex == -1))
223            return;
224
225        Point dropPoint = e.getLocation();
226        overIndex = indexAtLocation(dropPoint.x, dropPoint.y);
227
228        repaint();
229    }
230
231    /**
232     * Triggered when a drop has happened over {@code dropTarget}.
233     * 
234     * @param e State that we'll need in order to handle the drop.
235     */
236    public void drop(DropTargetDropEvent e) {
237        // if the dragged ComponentHolder was dragged from another window we
238        // must do a behind-the-scenes transfer from its old ComponentGroup to 
239        // the end of the new ComponentGroup.
240        if (outsideDrag) {
241            DataFlavor[] flave = e.getCurrentDataFlavors();
242            DraggableTabbedPane other = ((DraggableTabFlavor)flave[0]).getDragTab();
243
244            ComponentHolder target = other.removeDragged();
245            sourceIndex = group.quietAddComponent(target);
246            outsideDrag = false;
247        }
248
249        // check to see if we've actually dropped something McV understands.
250        if (sourceIndex >= 0) {
251            e.acceptDrop(VALID_ACTION);
252            Point dropPoint = e.getLocation();
253            int dropIndex = indexAtLocation(dropPoint.x, dropPoint.y);
254
255            // make sure the user chose to drop over a valid area/thing first
256            // then do the actual drop.
257            if ((dropIndex != -1) && (getComponentAt(dropIndex) != null))
258                doDrop(sourceIndex, dropIndex);
259
260            // clean up anything associated with the current drag and drop
261            e.getDropTargetContext().dropComplete(true);
262            sourceIndex = -1;
263            overIndex = -1;
264
265            repaint();
266        }
267    }
268
269    /**
270     * {@literal "Quietly"} removes the dragged component from its group. If the
271     * last component in a group has been dragged out of the group, the 
272     * associated window will be killed.
273     * 
274     * @return The removed component.
275     */
276    private ComponentHolder removeDragged() {
277        ComponentHolder removed = group.quietRemoveComponentAt(sourceIndex);
278
279        // no point in keeping an empty window around... but killing the 
280        // window here doesn't properly terminate the drag and drop (as this
281        // method is typically called from *another* window).
282        return removed;
283    }
284
285    /**
286     * Moves a component to its new index within the component group.
287     * 
288     * @param srcIdx The old index of the component.
289     * @param dstIdx The new index of the component.
290     */
291    public void doDrop(int srcIdx, int dstIdx) {
292        List<ComponentHolder> comps = group.getDisplayComponents();
293        ComponentHolder src = comps.get(srcIdx);
294
295        group.removeComponent(src);
296        group.addComponent(src, dstIdx);
297    }
298
299    /**
300     * Overridden so that McIDAS-V can draw an indicator of a dragged tab's 
301     * possible 
302     */
303    @Override public void paint(Graphics g) {
304        super.paint(g);
305
306        if (overIndex == -1)
307            return;
308
309        Rectangle bounds = getBoundsAt(overIndex);
310
311        if (bounds != null)
312            g.drawImage(INDICATOR, bounds.x-7, bounds.y, null);
313    }
314
315    /**
316     * Overriden so that McIDAS-V can change the window title upon changing
317     * tabs.
318     */
319    @Override public void setSelectedIndex(int index) {
320        super.setSelectedIndex(index);
321
322        // there are only ever component holders in the display comps.
323        @SuppressWarnings("unchecked")
324        List<ComponentHolder> comps = group.getDisplayComponents();
325
326        ComponentHolder h = comps.get(index);
327        String newTitle = 
328            UIManager.makeTitle(idv.getStateManager().getTitle(), h.getName());
329        if (window != null)
330            window.setTitle(newTitle);
331    }
332
333    /**
334     * Used to simply provide a reference to the originating 
335     * DraggableTabbedPane while we're dragging and dropping.
336     */
337    private static class TransferableIndex implements Transferable {
338        private DraggableTabbedPane tabbedPane;
339
340        private int index;
341
342        public TransferableIndex(DraggableTabbedPane dt, int i) {
343            tabbedPane = dt;
344            index = i;
345        }
346
347        // whatever is returned here needs to be serializable. so we can't just
348        // return the tabbedPane. :(
349        public Object getTransferData(DataFlavor flavor) {
350            return index;
351        }
352
353        public DataFlavor[] getTransferDataFlavors() {
354            return new DataFlavor[] { new DraggableTabFlavor(tabbedPane) };
355        }
356
357        public boolean isDataFlavorSupported(DataFlavor flavor) {
358            return true;
359        }
360    }
361
362    /**
363     * To be perfectly honest I'm still a bit fuzzy about DataFlavors. As far 
364     * as I can tell they're used like so: if a user dragged an image file on
365     * to a toolbar, the toolbar might be smart enough to add the image. If the
366     * user dragged the same image file into a text document, the text editor
367     * might be smart enough to insert the path to the image or something.
368     * 
369     * I'm thinking that would require two data flavors: some sort of toolbar
370     * flavor and then some sort of text flavor?
371     */
372    private static class DraggableTabFlavor extends DataFlavor {
373        private DraggableTabbedPane tabbedPane;
374
375        public DraggableTabFlavor(DraggableTabbedPane dt) {
376            super(DraggableTabbedPane.class, "DraggableTabbedPane");
377            tabbedPane = dt;
378        }
379
380        public DraggableTabbedPane getDragTab() {
381            return tabbedPane;
382        }
383    }
384
385    /**
386     * Handle the user dropping a tab outside of a McV window. This will create
387     * a new window and add the dragged tab to the ComponentGroup within the
388     * newly created window. The new window is the same size as the origin 
389     * window, with the top centered over the location where the user released
390     * the mouse.
391     * 
392     * @param dragged The ComponentHolder that's being dragged around.
393     * @param drop The x- and y-coordinates where the user dropped the tab.
394     */
395    private void newWindowDrag(ComponentHolder dragged, Point drop) {
396        //      if ((dragged == null) || (window == null))
397        if (dragged == null)
398            return;
399
400        UIManager ui = (UIManager)idv.getIdvUIManager();
401
402        try {
403            Element skinRoot = XmlUtil.getRoot(Constants.BLANK_COMP_GROUP, getClass());
404
405            // create the new window with visibility off, so we can position 
406            // the window in a sensible way before the user has to see it.
407            IdvWindow w = ui.createNewWindow(null, false, "McIDAS-V", 
408                    Constants.BLANK_COMP_GROUP, 
409                    skinRoot, false, null);
410
411            // make the new window the same size as the old and center the 
412            // *top* of the window over the drop point.
413            int height = window.getBounds().height;
414            int width = window.getBounds().width;
415            int startX = drop.x - (width / 2);
416
417            w.setBounds(new Rectangle(startX, drop.y, width, height));
418
419            // be sure to add the dragged component holder to the new window.
420            ComponentGroup newGroup = 
421                (ComponentGroup)w.getComponentGroups().get(0);
422
423            newGroup.addComponent(dragged);
424
425            // let there be a window
426            w.setVisible(true);
427        } catch (Throwable e) {
428            e.printStackTrace();
429        }
430    }
431
432    /**
433     * Handles what happens at the very end of a drag and drop. Since I could
434     * not find a better method for it, tabs that are dropped outside of a McV
435     * window are handled with this method.
436     */
437    public void dragDropEnd(DragSourceDropEvent e) {
438        if (!e.getDropSuccess() && e.getDropAction() == 0) {
439            newWindowDrag(removeDragged(), e.getLocation());
440        }
441
442        // this should probably be the last thing to happen in this method.
443        // checks to see if we've got a blank window after a drag and drop; 
444        // if so, dispose!
445        List<ComponentHolder> comps = group.getDisplayComponents();
446        if (comps == null || comps.isEmpty()) {
447            window.dispose();
448        }
449    }
450
451    // required methods that we don't need to implement yet.
452    public void dragEnter(DragSourceDragEvent e) { }
453    public void dragExit(DragSourceEvent e) { }
454    public void dragOver(DragSourceDragEvent e) { }
455    public void dropActionChanged(DragSourceDragEvent e) { }
456    public void dropActionChanged(DropTargetDragEvent e) { }
457
458    public void mouseClicked(final MouseEvent e) {
459        processMouseEvents(e);
460    }
461
462    public void mouseExited(final MouseEvent e) {
463        processMouseEvents(e);
464    }
465
466    public void mousePressed(final MouseEvent e) {
467        processMouseEvents(e);
468    }
469
470    public void mouseEntered(final MouseEvent e) {
471        processMouseEvents(e);
472    }
473
474    public void mouseMoved(final MouseEvent e) {
475        processMouseEvents(e);
476    }
477
478    public void mouseDragged(final MouseEvent e) {
479        processMouseEvents(e);
480    }
481
482    public void mouseReleased(final MouseEvent e) {
483        processMouseEvents(e);
484    }
485
486    private void processMouseEvents(final MouseEvent e) {
487        int eventX = e.getX();
488        int eventY = e.getY();
489
490        int tabIndex = getUI().tabForCoordinate(this, eventX, eventY);
491        if (tabIndex < 0)
492            return;
493
494        TabButton icon = (TabButton)getIconAt(tabIndex);
495        if (icon == null)
496            return;
497
498        int id = e.getID();
499        Rectangle iconBounds = icon.getBounds();
500        if (!iconBounds.contains(eventX, eventY) || id == MouseEvent.MOUSE_EXITED) {
501            if (icon.getState() == ButtonState.ROLLOVER || icon.getState() == ButtonState.PRESSED)
502                icon.setState(ButtonState.DEFAULT);
503
504            if (e.getClickCount() >= 2 && !e.isPopupTrigger() && id == MouseEvent.MOUSE_CLICKED)
505                group.renameDisplay(tabIndex);
506
507            repaint(iconBounds);
508            return;
509        }
510
511        if (id == MouseEvent.MOUSE_PRESSED && (e.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) != 0) {
512            icon.setState(ButtonState.PRESSED);
513        } else if (id == MouseEvent.MOUSE_CLICKED) {
514            icon.setState(ButtonState.DEFAULT);
515            group.destroyDisplay(tabIndex);
516        } else {
517            icon.setState(ButtonState.ROLLOVER);
518        }
519        repaint(iconBounds);
520    }
521
522    @Override public void addTab(String title, Component component) {
523        addTab(title, component, null);
524    }
525
526    public void addTab(String title, Component component, Icon extraIcon) {
527        if (getTabCount() < 9)
528            title = "<html><font color=\""+currentTabColor+"\">"+(getTabCount()+1)+"</font> "+title+"</html>";
529        else if (getTabCount() == 9)
530            title = "<html><font color=\""+currentTabColor+"\">0</font> "+title+"</html>";
531        super.addTab(title, new TabButton(), component);
532    }
533
534    private static final Color unselected = new Color(165, 165, 165);
535    private static final Color selected = new Color(225, 225, 225);
536
537    private static final String indexColorMetal = "#AAAAAA";
538    private static final String indexColorUglyTabs = "#708090";
539    private String currentTabColor = indexColorMetal;
540
541    class CloseableTabbedPaneUI extends BasicTabbedPaneUI {
542        private int horizontalTextPosition = SwingUtilities.LEFT;
543
544        public CloseableTabbedPaneUI() { }
545
546        public CloseableTabbedPaneUI(int horizontalTextPosition) {
547            this.horizontalTextPosition = horizontalTextPosition;
548        }
549
550        @Override protected void layoutLabel(int tabPlacement, 
551            FontMetrics metrics, int tabIndex, String title, Icon icon, 
552            Rectangle tabRect, Rectangle iconRect, Rectangle textRect, 
553            boolean isSelected) 
554        {
555            if (tabPane.getTabCount() == 0)
556                return;
557
558            textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
559            javax.swing.text.View v = getTextViewForTab(tabIndex);
560            if (v != null)
561                tabPane.putClientProperty("html", v);
562
563            SwingUtilities.layoutCompoundLabel((JComponent)tabPane,
564                    metrics, title, icon,
565                    SwingUtilities.CENTER,
566                    SwingUtilities.CENTER,
567                    SwingUtilities.CENTER,
568                    horizontalTextPosition,
569                    tabRect,
570                    iconRect,
571                    textRect,
572                    textIconGap + 2);
573
574            int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
575            int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
576            iconRect.x += xNudge;
577            iconRect.y += yNudge;
578            textRect.x += xNudge;
579            textRect.y += yNudge;
580        }
581
582        @Override protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
583            if (!isSelected) {
584                g.setColor(unselected);
585            } else {
586                g.setColor(selected);
587            }
588
589            g.fillRect(x, y, w, h);
590            g.setColor(selected);
591            g.drawLine(x, y, x, y+h);
592        }
593    }
594
595    class CloseableMetalTabbedPaneUI extends MetalTabbedPaneUI {
596
597        private int horizontalTextPosition = SwingUtilities.LEFT;
598
599        public CloseableMetalTabbedPaneUI() { }
600
601        public CloseableMetalTabbedPaneUI(int horizontalTextPosition) {
602            this.horizontalTextPosition = horizontalTextPosition;
603        }
604
605        @Override protected void layoutLabel(int tabPlacement, 
606            FontMetrics metrics, int tabIndex, String title, Icon icon, 
607            Rectangle tabRect, Rectangle iconRect, Rectangle textRect, 
608            boolean isSelected) 
609        {
610            if (tabPane.getTabCount() == 0)
611                return;
612
613            textRect.x = 0;
614            textRect.y = 0;
615            iconRect.x = 0;
616            iconRect.y = 0;
617
618            javax.swing.text.View v = getTextViewForTab(tabIndex);
619            if (v != null)
620                tabPane.putClientProperty("html", v);
621
622            SwingUtilities.layoutCompoundLabel((JComponent)tabPane,
623                    metrics, title, icon,
624                    SwingUtilities.CENTER,
625                    SwingUtilities.CENTER,
626                    SwingUtilities.CENTER,
627                    horizontalTextPosition,
628                    tabRect,
629                    iconRect,
630                    textRect,
631                    textIconGap + 2);
632
633            int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
634            int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
635            iconRect.x += xNudge;
636            iconRect.y += yNudge;
637            textRect.x += xNudge;
638            textRect.y += yNudge;
639        }
640    }
641
642    public static class TabButton implements Icon {
643        public enum ButtonState { DEFAULT, PRESSED, DISABLED, ROLLOVER };
644        private static final Map<ButtonState, String> iconPaths = new HashMap<ButtonState, String>();
645
646        private ButtonState currentState = ButtonState.DEFAULT;
647        private int iconWidth = 0;
648        private int iconHeight = 0;
649
650        private int posX = 0;
651        private int posY = 0;
652
653        public TabButton() {
654            setStateIcon(ButtonState.DEFAULT, "/edu/wisc/ssec/mcidasv/resources/icons/closetab/metal_close_enabled.png");
655            setStateIcon(ButtonState.PRESSED, "/edu/wisc/ssec/mcidasv/resources/icons/closetab/metal_close_pressed.png");
656            setStateIcon(ButtonState.ROLLOVER, "/edu/wisc/ssec/mcidasv/resources/icons/closetab/metal_close_rollover.png");
657            setState(ButtonState.DEFAULT);
658        }
659
660        public static Icon getStateIcon(final ButtonState state) {
661            String path = iconPaths.get(state);
662            if (path == null)
663                path = iconPaths.get(ButtonState.DEFAULT);
664            return GuiUtils.getImageIcon(path);
665        }
666
667        public static void setStateIcon(final ButtonState state, final String path) {
668            iconPaths.put(state, path);
669        }
670
671        public static String getStateIconPath(final ButtonState state) {
672            if (!iconPaths.containsKey(state))
673                return iconPaths.get(ButtonState.DEFAULT);
674            return iconPaths.get(state);
675        }
676
677        public void setState(final ButtonState state) {
678            currentState = state;
679            Icon currentIcon = getStateIcon(state);
680            if (currentIcon == null)
681                return;
682
683            iconWidth = currentIcon.getIconWidth();
684            iconHeight = currentIcon.getIconHeight();
685        }
686
687        public ButtonState getState() {
688            return currentState;
689        }
690
691        public Icon getIcon() {
692            return getStateIcon(currentState);
693        }
694
695        public void paintIcon(Component c, Graphics g, int x, int y) {
696            Icon current = getIcon();
697            if (current == null)
698                return;
699
700            posX = x;
701            posY = y;
702            current.paintIcon(c, g, x, y);
703        }
704
705        public int getIconWidth() {
706            return iconWidth;
707        }
708
709        public int getIconHeight() {
710            return iconHeight;
711        }
712
713        public Rectangle getBounds() {
714            return new Rectangle(posX, posY, iconWidth, iconHeight);
715        }
716    }
717}