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