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