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}