001 /*
002 * $Id: DraggableTabbedPane.java,v 1.33 2012/02/19 17:35:51 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2012
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
031 package edu.wisc.ssec.mcidasv.ui;
032
033 import java.awt.Color;
034 import java.awt.Component;
035 import java.awt.Cursor;
036 import java.awt.FontMetrics;
037 import java.awt.Graphics;
038 import java.awt.Image;
039 import java.awt.Point;
040 import java.awt.Rectangle;
041 import java.awt.datatransfer.DataFlavor;
042 import java.awt.datatransfer.Transferable;
043 import java.awt.dnd.DnDConstants;
044 import java.awt.dnd.DragGestureEvent;
045 import java.awt.dnd.DragGestureListener;
046 import java.awt.dnd.DragSource;
047 import java.awt.dnd.DragSourceDragEvent;
048 import java.awt.dnd.DragSourceDropEvent;
049 import java.awt.dnd.DragSourceEvent;
050 import java.awt.dnd.DragSourceListener;
051 import java.awt.dnd.DropTarget;
052 import java.awt.dnd.DropTargetDragEvent;
053 import java.awt.dnd.DropTargetDropEvent;
054 import java.awt.dnd.DropTargetEvent;
055 import java.awt.dnd.DropTargetListener;
056 import java.awt.event.InputEvent;
057 import java.awt.event.MouseEvent;
058 import java.awt.event.MouseListener;
059 import java.awt.event.MouseMotionListener;
060 import java.util.HashMap;
061 import java.util.List;
062 import java.util.Map;
063
064 import javax.swing.Icon;
065 import javax.swing.ImageIcon;
066 import javax.swing.JComponent;
067 import javax.swing.JTabbedPane;
068 import javax.swing.SwingUtilities;
069 import javax.swing.plaf.basic.BasicTabbedPaneUI;
070 import javax.swing.plaf.metal.MetalTabbedPaneUI;
071
072 import org.w3c.dom.Element;
073
074 import ucar.unidata.idv.IntegratedDataViewer;
075 import ucar.unidata.idv.ui.IdvWindow;
076 import ucar.unidata.ui.ComponentGroup;
077 import ucar.unidata.ui.ComponentHolder;
078 import ucar.unidata.util.GuiUtils;
079 import ucar.unidata.xml.XmlUtil;
080 import edu.wisc.ssec.mcidasv.Constants;
081 import 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 */
087 public 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 }