/* Copyright (C) 2006 Christian Schneider * * This file is part of Nomad. * * Nomad is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Nomad is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Nomad; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package net.sf.nmedit.nomad.core.swing.tabs; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.Stroke; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DragGestureRecognizer; import java.awt.dnd.DragSource; import java.awt.dnd.DragSourceContext; import java.awt.dnd.DragSourceDragEvent; import java.awt.dnd.DragSourceDropEvent; import java.awt.dnd.DragSourceEvent; import java.awt.dnd.DragSourceListener; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetContext; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.dnd.InvalidDnDOperationException; import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.AbstractAction; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JPopupMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.KeyStroke; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.ColorUIResource; import net.sf.nmedit.nmutils.Platform; import net.sf.nmedit.nmutils.swing.NMLazyActionMap; import net.sf.nmedit.nmutils.swing.NmSwingUtilities; import net.sf.nmedit.nomad.core.Nomad; import net.sf.nmedit.nomad.core.menulayout.MLEntry; import net.sf.nmedit.nomad.core.misc.FocusStroke; public class FFTabBarUI extends TabBarUI { public static final int BACKWARDS_TAB = -2; public static final int FORWARD_TAB = -3; public static final int DROP_DOWN_TAB = -4; public static final Color DEFAULT_BG_GRADIENT_TOP = new ColorUIResource( 0xcbc8c4); public static final Color DEFAULT_BG_GRADIENT_MIDDLE = new ColorUIResource( 0xeae7e2); public static final Color DEFAULT_BG_GRADIENT_BOTTOM = new ColorUIResource( 0xdddad5); public static final float BG_GRADIENT_MIDDLE = 1f - 0.3333f; public static final Color DEFAULT_BORDER_LINE_COLOR = new ColorUIResource( 0xb1a598); public static final Color DEFAULT_BORDER_FILL_COLOR = new ColorUIResource( 0xefece8); public static final int DEFAULT_BORDER_FILL_HEIGHT = 2; protected JTabBar tabBar; protected Color bgGradientTop = DEFAULT_BG_GRADIENT_TOP; protected Color bgGradientMiddle = DEFAULT_BG_GRADIENT_MIDDLE; protected Color bgGradientBottom = DEFAULT_BG_GRADIENT_BOTTOM; protected GradientPaint bgGradientTopMiddle; protected GradientPaint bgGradientMiddleBottom; protected boolean bgGradientEnabled = true; protected Color tabBgUpperGradientTop = new Color(0xe2e0de); protected Color tabBgUpperGradientBottom = new Color(0xdddbd7); protected Color tabBgLowerGradientTop = new Color(0xd8d6d2); protected Color tabBgLowerGradientBottom = new Color(0xe5e2dd); protected GradientPaint tabBgUpperGradient = null; protected GradientPaint tabBgLowerGradient = null; protected Color tabBgUpperGradientTopSelected = new Color(0xfaf9f7); protected Color tabBgUpperGradientBottomSelected = new Color(0xebe9e6); protected Color tabBgLowerGradientTopSelected = new Color(0xe7e5e2); protected Color tabBgLowerGradientBottomSelected = new Color(0xe8e6e2); protected GradientPaint tabBgUpperGradientSelected = null; protected GradientPaint tabBgLowerGradientSelected = null; protected int tabBgGradientHeight = -1; protected int tabBgGradientMid; protected Color lineColor = Color.decode("#AAAAAA"); protected Color tabBorderOuterSelected = new Color(0xa3a19e); protected Color tabBorderInnerSelected = new Color(0xf3f2f0); protected Color tabBorderOuter = new Color(0xbfbdba); protected Color tabBorderInner = new Color(0xf1f0ed); protected Color borderLineColor = DEFAULT_BORDER_LINE_COLOR; protected Color borderFillColor = DEFAULT_BORDER_FILL_COLOR; protected int borderFillHeight = DEFAULT_BORDER_FILL_HEIGHT; private int prevHeight = -1; private int prevWidth = -1; private boolean resized = false; protected int tabPaddingTop = 2; protected int tabPaddingLeftRight = 3; private int prevTabCount = -1; private int dndPreviousSelectionIndex = -1; protected transient Insets tmpInsets; private Dimension specialTabSize = new Dimension(); protected Rectangle tabBounds = new Rectangle(); protected Rectangle innerTabBounds = new Rectangle(); // tabBounds+tabInsets protected Rectangle closeButtonBounds = new Rectangle(); protected int minTabWidth = 16; protected int maxTabWidth = 180; protected Insets iconPadding = new Insets(2, 2, 2, 2); protected Insets textPadding = new Insets(2, 2, 2, 2); protected Insets tabInsets = new Insets(2, 2, 1, 2); protected int hoverIndex = -1; protected int closeIconHoverIndex = -1; protected boolean specialButtonsVisible = false; protected int dropTargetIndex = -1; // icons public final static Icon defaultIcon = getIcon2("mimetypes/text-x-generic.png"); final static Icon navBackwardEnabledIcon = getIcon("backward-enabled.png"); final static Icon navBackwardDisabledIcon = getIcon("backward-disabled.png"); final static Icon navForwardEnabledIcon = getIcon("forward-enabled.png"); final static Icon navForwardDisabledIcon = getIcon("forward-disabled.png"); final static Icon navDropDownIcon = getIcon("tab-down-arrow.png"); protected static Icon navDropDownDisabledIcon; final static Icon close_dis_hl = getIcon("close_btn_dis_hl.png"); final static Icon close_dis = getIcon("close_btn_dis.png"); final static Icon close_en_hl = getIcon("close_btn_en_hl.png"); final static Icon close_en = getIcon("close_btn_en.png"); final static Icon dropTabArrow = getIcon("dropTabArrow.png"); private static final String PATH_PREFIX = "/swing/jtab/"; private static Icon getIcon(String name) { URL url = FFTabBarUI.class.getResource(PATH_PREFIX+name); if (url == null) return null; return new ImageIcon(url); } private static Icon getIcon2(String name) { final String PATH_PREFIX2 = "/icons/tango/16x16/"; URL url = Nomad.sharedInstance().getClass().getResource(PATH_PREFIX2+name); if (url == null) return null; return new ImageIcon(url); } // protected MetalTabbedPaneUI public FFTabBarUI(JTabBar bar) { this.tabBar = bar; } protected void setDropTargetIndex(int index) { if (index<0) index = -1; if (dropTargetIndex != index) { this.dropTargetIndex = index; tabBar.repaint(); } } protected void setHoverIndex(int hoverIndex) { if (this.hoverIndex != hoverIndex) { this.hoverIndex = hoverIndex; tabBar.repaint(); } } // **** ComponentUI public static FFTabBarUI createUI(JComponent c) { return new FFTabBarUI((JTabBar) c); } public void installUI(JComponent c) { checkComponent(c); installUI(); } public void uninstallUI(JComponent c) { checkComponent(c); uninstallUI(); } // **** ComponentUI protected boolean resized() { return resized; } protected void setResized(boolean resized) { this.resized = resized; prevWidth = tabBar.getWidth(); prevHeight = tabBar.getHeight(); } protected void updateResizedFlag() { setResized(prevWidth != tabBar.getWidth() || prevHeight != tabBar.getHeight()); } protected void checkComponent(JComponent c) { if (c != tabBar) throw new IllegalArgumentException(); } public void installUI() { BasicEventHandler eventHandler = createEventHandler(tabBar); if (eventHandler != null) eventHandler.install(); // TODO lookup settings in UIDefaults tabBar.setBackground(bgGradientBottom); tabBar.setBorder(new FFTabBorder(borderLineColor, borderFillColor, borderFillHeight)); tabBar.setFocusable(true); if (navDropDownDisabledIcon == null) navDropDownDisabledIcon = UIManager.getLookAndFeel() .getDisabledIcon(tabBar, navDropDownIcon); } public void uninstallUI() { BasicEventHandler eventHandler = lookupEventHandler(tabBar); if (eventHandler != null) eventHandler.uninstall(); tabBar.setFocusable(false); } // **** painting public void update(Graphics g, JComponent c) { paint(g, c); } protected void checkUpdate() { tmpInsets = tabBar.getInsets(tmpInsets); updateResizedFlag(); boolean fontChanged = updateFont(tabBar.getFont()); if (prevTabCount != tabBar.getTabCount() || resized() || fontChanged) { computeTabBounds(); } } public void paint(Graphics g, JComponent c) { if (c.getHeight()<=0 || c.getWidth()<=0) return ; checkComponent(c); checkUpdate(); paint2((Graphics2D) g); setResized(false); } private void paint2(Graphics2D g2) { paintBackground(g2); paintTabs(g2); if (specialButtonsVisible) paintSpecialButtons(g2); if (dropTargetIndex>=0) paintDropTarget(g2); } private void paintDropTarget(Graphics2D g2) { if (dropTargetIndex<0) return; int tx = tabBounds.x+(dropTargetIndex*tabBounds.width); tx-=dropTabArrow.getIconWidth()/2; dropTabArrow.paintIcon(tabBar, g2, tx, tabBounds.y+(tabBounds.height-dropTabArrow.getIconHeight())/2); } protected void computeTabBounds() { int minw = Math.max(this.minTabWidth, (16 + iconPadding.left + iconPadding.right) * 2 // icon+close-icon ); int textHeight = Math.max(fontMetrics.getHeight(), selectedfontMetrics .getHeight()) + textPadding.top + textPadding.bottom; int iconHeight = 16 + iconPadding.top + iconPadding.bottom; int tabCount = tabBar.getTabCount(); int innerWidth = tabBar.getWidth()-tmpInsets.left-tmpInsets.right-tabPaddingLeftRight-tabPaddingLeftRight; int width; if (tabCount == 0) width = 0; else if (tabCount * maxTabWidth <= innerWidth) width = maxTabWidth; else if (tabCount*minw >= innerWidth) width = minw; else { width = innerWidth / tabCount; } int height = tabInsets.top + tabInsets.bottom + Math.max(iconHeight, textHeight)+1; if (tabBounds == null) tabBounds = new Rectangle(); tabBounds.setSize(width, height); specialButtonsVisible = tabCount > 0 && (width * tabCount > tabBar.getWidth()); if (specialTabSize == null) specialTabSize = new Dimension(); int specialIconWidth = Math.max(navForwardEnabledIcon.getIconHeight(), Math.max(navBackwardEnabledIcon.getIconWidth(), navDropDownIcon.getIconWidth())); specialTabSize.height = tabBounds.height; specialTabSize.width = tabInsets.left + tabInsets.right + specialIconWidth + iconPadding.left + iconPadding.right; // compute visible tabs int dx = 0; int selectedIndex = tabBar.getSelectedIndex(); if (specialButtonsVisible) { dx = specialTabSize.width; } int minIndex = 0; int maxIndex = tabBar.getTabCount()-1; int selX = selectedIndex*tabBounds.width; while (dx+selX+tabBounds.width>tabBar.getWidth()) { minIndex++; dx -= tabBounds.width; if (minIndex>selectedIndex) break; } if (specialButtonsVisible) { if (dx+selX+tabBounds.width>tabBar.getWidth()-specialTabSize.width*2) dx-= specialTabSize.width*2; } while (dx+maxIndex*tabBounds.width>tabBar.getWidth()) { maxIndex--; if (maxIndex<0) break; } tabRunStart = minIndex; tabRunStop = maxIndex; tabBounds.x = dx+(specialButtonsVisible?0:tabPaddingLeftRight); tabBounds.y = tabPaddingTop+tmpInsets.top; innerTabBounds.setBounds(tabBounds.x+tabInsets.left, tabBounds.y+tabInsets.top, tabBounds.width-tabInsets.left-tabInsets.right, tabBounds.height-tabInsets.top-tabInsets.bottom); Icon closeButton = close_en_hl; closeButtonBounds.setSize(closeButton.getIconWidth(), closeButton.getIconHeight()); closeButtonBounds.setLocation(getCloseButtonX(tabBounds.x, closeButton), getCloseButtonY(tabBounds.y, closeButton)); } protected int getCloseButtonX(int xoffset, Icon icon) { return xoffset + tabBounds.width - icon.getIconWidth() - iconPadding.right - tabInsets.right; } protected int getCloseButtonY(int yoffset, Icon icon) { return yoffset+(tabBounds.height-icon.getIconHeight())/2; } private int tabRunStart = 0; private int tabRunStop = -1; private transient Font font; private transient Font selectedFont; private transient FontMetrics fontMetrics; private transient FontMetrics selectedfontMetrics; public boolean updateFont(Font font) { boolean updateSelectedFont = selectedFont == null; boolean fontChanged = false; if (this.font != font) { this.font = font; this.fontMetrics = tabBar.getFontMetrics(font); updateSelectedFont = true; fontChanged = true; } if (updateSelectedFont) { selectedFont = new Font(font.getName(), Font.BOLD, font.getSize()); selectedfontMetrics = tabBar.getFontMetrics(selectedFont); } return fontChanged || updateSelectedFont; } public void showDropDownMenu(MouseEvent e) { JPopupMenu popup = new JPopupMenu(); for (int i=0;i<tabBar.getTabCount();i++) { popup.add(new JRadioButtonMenuItem(new SelectTabAction(i))); } popup.show(tabBar, e.getX(), e.getY()); } private class SelectTabAction extends AbstractAction implements Runnable { /** * */ private static final long serialVersionUID = 7325111901000279179L; private int tabIndex; public SelectTabAction(int tabIndex) { this.tabIndex = tabIndex; putValue(NAME, tabBar.getTitleAt(tabIndex)); putValue(MLEntry.SELECTED_KEY, tabIndex == tabBar.getSelectedIndex()); putValue(SHORT_DESCRIPTION, tabBar.getToolTipTextAt(tabIndex)); putValue(SMALL_ICON, tabBar.getIconAt(tabIndex)); putValue(MLEntry.DISPLAYED_MNEMONIC_INDEX_KEY, tabBar.getDisplayedMnemonicIndexAt(tabIndex)); putValue(MNEMONIC_KEY, tabBar.getMnemonicAt(tabIndex)); } public void actionPerformed(ActionEvent e) { SwingUtilities.invokeLater(this); } public void run() { if (tabIndex>=0 && tabIndex<tabBar.getTabCount()) { tabBar.setSelectedIndex(tabIndex); tabIndex = -1; } } } public String getToolTipTextAt(JTabBar<?> c, int x, int y) { int index = getTabIndexForLocation(x, y); if (index >= 0) { return tabBar.getToolTipTextAt(index); } if (index == DROP_DOWN_TAB) return "All Tabs"; return c.getToolTipText(); } private void paintTabs(Graphics2D g2) { if (tabBar.getTabCount() <= 0) return; int selectedIndex = tabBar.getSelectedIndex(); boolean enabled = tabBar.isEnabled(); for (int i = tabRunStop; i > selectedIndex; i--) { boolean e = enabled ? tabBar.isEnabledAt(i) : enabled; paintTab(g2, i, false, e); } for (int i = selectedIndex - 1; i >= tabRunStart; i--) { boolean e = enabled ? tabBar.isEnabledAt(i) : enabled; paintTab(g2, i, false, e); } int bottom = tabBounds.y+tabBounds.height-1; //tabBar.getHeight() - 1 - tmpInsets.bottom; g2.setColor(borderLineColor); g2.drawLine(0, bottom, tabBar.getWidth(), bottom); if (selectedIndex >= 0) { boolean e = enabled ? tabBar.isEnabledAt(selectedIndex) : enabled; paintTab(g2, selectedIndex, true, e); } } protected void paintSpecialButtons(Graphics2D g2) { Icon navBackIcon, navForwIcon, navDDIcon; int selectedIndex = tabBar.getSelectedIndex(); boolean navBackEnabled = false; boolean navForwEnabled = false; boolean navDDEnabled = false; if (tabBar.isEnabled()) { navBackEnabled = selectedIndex > 0; navForwEnabled = selectedIndex < tabBar.getTabCount() - 1; navDDEnabled = true; } navBackIcon = navBackEnabled ? navBackwardEnabledIcon : navBackwardDisabledIcon; navForwIcon = navForwEnabled ? navForwardEnabledIcon : navForwardDisabledIcon; navDDIcon = navDDEnabled ? navDropDownIcon : navDropDownDisabledIcon; int w = specialTabSize.width; int top = tmpInsets.left + tabPaddingTop; int iconTop = top + iconPadding.top; // back button paintTabBackground(g2, tmpInsets.left, top, w, tabBounds.height, false, hoverIndex == BACKWARDS_TAB); navBackIcon.paintIcon(tabBar, g2, (w-navBackIcon.getIconWidth())/2, iconTop + (tabBounds.height - navBackIcon.getIconHeight()) / 2); int ddx = tabBar.getWidth() - tmpInsets.right - w; // right button paintTabBackground(g2, ddx - w, top, w, tabBounds.height, false, hoverIndex == FORWARD_TAB); navForwIcon.paintIcon(tabBar, g2, ddx - w +(w-navForwIcon.getIconWidth())/2 /*+ iconPadding.left + (iw - navForwIcon.getIconWidth()) / 2*/, iconTop + (tabBounds.height - navForwIcon.getIconHeight()) / 2); // dd button paintTabBackground(g2, ddx, top, w, tabBounds.height, false, hoverIndex == DROP_DOWN_TAB); navDDIcon.paintIcon(tabBar, g2, ddx + (w-navDDIcon.getIconWidth())/2, iconTop + (tabBounds.height - navDDIcon.getIconHeight()) / 2); } private void paintTab(Graphics2D g2, int index, boolean selected, boolean enabled) { Icon icon; if (tabBar.isEnabledAt(index)) { icon = tabBar.getIconAt(index); } else { icon = tabBar.getDisabledIconAt(index); } String title = tabBar.getTitleAt(index); if (title != null && title.length() == 0) title = null; int mnemonic = tabBar.getMnemonicAt(index); int mnemonicIndex = tabBar.getMnemonicAt(index); paintTab(g2, index, icon, title, mnemonic, mnemonicIndex, selected, hoverIndex == index, enabled, tabBounds.x+(index * tabBounds.width), tabBounds.y, tabBounds.width, tabBounds.height); } private static Rectangle paintIconR = new Rectangle(); private static Rectangle paintTextR = new Rectangle(); private static Rectangle paintViewR = new Rectangle(); private void paintTab(Graphics2D g2, int index, Icon icon, String title, int mnemonic, int mnemonicIndex, boolean selected, boolean hovered, boolean enabled, int x, int y, int width, int height) { paintTabBackground(g2, x, y, width, height, selected, hovered); if ((icon == null) && (title == null)) { return; } int closeIconX = Integer.MAX_VALUE; int closeIconWidth = 0; if (tabBar.isCloseActionEnabled()) { Icon closeIcon; if (selected) { closeIcon = hovered && closeIconHoverIndex==index ? close_en_hl : close_en; } else { closeIcon = hovered && closeIconHoverIndex==index ? close_dis_hl : close_dis; } closeIconX = getCloseButtonX(tabBounds.x+(index*tabBounds.width), closeIcon); closeIconWidth = closeIcon.getIconWidth(); closeIcon.paintIcon(tabBar, g2, closeIconX, getCloseButtonY(tabBounds.y,closeIcon) ); } paintViewR.x = tabInsets.left + x + iconPadding.left; paintViewR.y = innerTabBounds.y; paintViewR.width = innerTabBounds.width - (closeIconWidth + iconPadding.left + iconPadding.right); paintViewR.height = innerTabBounds.height; paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0; paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0; Font font; FontMetrics fm; if (selected) { font = this.selectedFont; fm = this.selectedfontMetrics; } else { font = this.font; fm = this.fontMetrics; } g2.setFont(font); String clippedText = layoutCL(tabBar, fm, title, icon, paintViewR, paintIconR, paintTextR); if (icon != null) icon.paintIcon(tabBar, g2, paintIconR.x, paintIconR.y); if (title != null) { int textX = paintTextR.x; int textY = paintTextR.y + fm.getAscent(); if (enabled) { paintEnabledText(g2, clippedText, mnemonicIndex, textX, textY); if (selected && tabBar.hasFocus()) { g2.setColor(Color.BLACK); // focus color // stroke int sx = paintTextR.x; int sy = paintTextR.y; int sh = paintTextR.height; int sr; if (closeIconWidth > 0 && closeIconX != Integer.MAX_VALUE) { sr = closeIconX-iconPadding.left; } else { sr = x+width-textPadding.right-tmpInsets.right-tabInsets.right; } int sw = sr-sx; Stroke oldStroke = g2.getStroke(); g2.setStroke(FocusStroke.getFocusStroke()); g2.drawRect(sx, sy, sw, sh); g2.setStroke(oldStroke); } } else { paintDisabledText(g2, clippedText, mnemonicIndex, textX, textY); } } } /** * Forwards the call to SwingUtilities.layoutCompoundLabel(). This method is * here so that a subclass could do Label specific layout and to shorten the * method name a little. * * @see SwingUtilities#layoutCompoundLabel */ protected String layoutCL(JComponent c, FontMetrics fontMetrics, String text, Icon icon, Rectangle viewR, Rectangle iconR, Rectangle textR) { return SwingUtilities.layoutCompoundLabel((JComponent) c, fontMetrics, text, icon, SwingConstants.CENTER, SwingConstants.LEADING, SwingConstants.CENTER, SwingConstants.TRAILING, /* * c.getVerticalAlignment(), c.getHorizontalAlignment(), * c.getVerticalTextPosition(), c.getHorizontalTextPosition(), */ viewR, iconR, textR, iconPadding.right + textPadding.left); } protected void paintEnabledText(Graphics g, String s, int mnemonicIndex, int textX, int textY) { g.setColor(tabBar.getForeground()); drawText(g, s, mnemonicIndex, textX, textY); } protected void paintDisabledText(Graphics g, String s, int mnemonicIndex, int textX, int textY) { Color c = tabBar.getBackground(); g.setColor(c.brighter()); drawText(g, s, mnemonicIndex, textX + 1, textY + 1); g.setColor(c.darker()); drawText(g, s, mnemonicIndex, textX, textY); } private void drawText(Graphics g, String s, int mnemonicIndex, int textX, int textY) { NmSwingUtilities.drawStringUnderlineCharAt(/* tabComponent, */g, s, mnemonicIndex, textX, textY); } protected void paintTabBackground(Graphics2D g2, int x, int y, int w, int h, boolean selected, boolean hovered) { if (tabBgGradientHeight < 0 || tabBgGradientHeight != h) { tabBgGradientHeight = h; tabBgGradientMid = tabBgGradientHeight / 2; tabBgUpperGradient = new GradientPaint(0, y, tabBgUpperGradientTop, 0, y + tabBgGradientMid, tabBgUpperGradientBottom); tabBgLowerGradient = new GradientPaint(0, y + tabBgGradientMid, tabBgLowerGradientTop, 0, y + h, tabBgLowerGradientBottom); tabBgUpperGradientSelected = new GradientPaint(0, y, tabBgUpperGradientTopSelected, 0, y + tabBgGradientMid, tabBgUpperGradientBottomSelected); tabBgLowerGradientSelected = new GradientPaint(0, y + tabBgGradientMid, tabBgLowerGradientTopSelected, 0, y + h, tabBgLowerGradientBottomSelected); } Paint bgUpperGradient, bgLowerGradient; Color outerBorder, innerBorder; if (hovered || selected) { bgUpperGradient = tabBgUpperGradientSelected; bgLowerGradient = tabBgLowerGradientSelected; outerBorder = tabBorderOuterSelected; innerBorder = tabBorderInnerSelected; } else { bgUpperGradient = tabBgUpperGradient; bgLowerGradient = tabBgLowerGradient; outerBorder = tabBorderOuter; innerBorder = tabBorderInner; } Paint oldPaint = g2.getPaint(); g2.setPaint(bgUpperGradient); g2.fillRect(x + 2, y + 2, w - 2, tabBgGradientMid - 2); g2.setPaint(bgLowerGradient); g2.fillRect(x + 2, y + tabBgGradientMid, w - 2, tabBgGradientMid); g2.setPaint(oldPaint); g2.setColor(innerBorder); drawTabBorder(g2, x + 1, y + 1, w - 2, h - 1); g2.setColor(outerBorder); drawTabBorder(g2, x, y, w, h-1); /* * g2.setColor(Color.red); g2.drawRect(x, y, w-1,h-1); */ } private void drawTabBorder(Graphics2D g2, int x, int y, int w, int h) { int b = y + h - 1; int r = x + w - 1; g2.drawLine(x + 1, y, r - 1, y); // top g2.drawLine(x, y + 1, x, b); // left g2.drawLine(r, y + 1, r, b); // right } private void paintBackground(Graphics2D g2) { if (!tabBar.isOpaque()) return; int w = tabBar.getWidth(); int h = tabBar.getHeight(); if (bgGradientEnabled) { float midY_px = h * BG_GRADIENT_MIDDLE; if (resized() || bgGradientTopMiddle == null || bgGradientMiddleBottom == null) { bgGradientTopMiddle = new GradientPaint(0, 0, bgGradientTop, 0, midY_px, bgGradientMiddle); bgGradientMiddleBottom = new GradientPaint(0, midY_px, bgGradientMiddle, 0, h, bgGradientBottom); } Paint prevPaint = g2.getPaint(); g2.setPaint(bgGradientTopMiddle); g2.fillRect(0, 0, w, (int) midY_px); g2.setPaint(bgGradientMiddleBottom); g2.fillRect(0, (int) midY_px, w, h - (int) (midY_px)); g2.setPaint(prevPaint); } else { g2.setColor(tabBar.getBackground()); g2.fillRect(0, 0, w, h); } } public int tabForCoordinate(JTabBar<?> tabBar, int x, int y) { if (this.tabBar != tabBar) return -1; int index = getTabIndexForLocation(x, y); return index<0 ? -1 : index; } protected boolean isInCloseButtonBounds(int x, int y) { int index = getTabIndexForLocation(x, y); if (index<0) return false; return closeButtonBounds.contains(x-index*tabBounds.width, y-tabBounds.y); } public int getCloseButtonHoverIndex(int x, int y) { if (!tabBar.isCloseActionEnabled()) return -1; int index = getTabIndexForLocation(x, y); return isInCloseButtonBounds(x, y) ? index : -1; } public void setCloseButtonHoverIndex(int tabIndex) { if (!tabBar.isCloseActionEnabled()) tabIndex = -1; if (this.closeIconHoverIndex != tabIndex) { this.closeIconHoverIndex = tabIndex; tabBar.repaint(); } } public int getTabIndexForLocation(int x, int y) { int w = tabBar.getWidth(); if (y < tabBounds.y || y > (tabBounds.y+tabBounds.height) || x < 0 || x > w) return -1; if (specialButtonsVisible) { if (x<specialTabSize.width) return BACKWARDS_TAB; if (x>w-specialTabSize.width*2) { if (x>w-specialTabSize.width) return DROP_DOWN_TAB; else return FORWARD_TAB; } } if (tabBounds.width<=0) // implies that there are no tabs return -1; int index = (x-tabBounds.x) / tabBounds.width; if (index < 0) return -1; if (index >= tabBar.getTabCount()) return -1; return index; } // **** dimensions public Dimension getPreferredSize(JComponent c) { checkComponent(c); checkUpdate(); int tabCount = tabBar.getTabCount(); Dimension size; int iw = tmpInsets.left + tmpInsets.right; int ih = tmpInsets.top + tmpInsets.bottom; int h = tabBounds.height + tabPaddingTop + ih; if (tabCount > 0) { size = new Dimension(iw + tabBounds.width * tabCount, h); } else { size = new Dimension(iw + tabBounds.width, h); } setResized(false); return size; } public Dimension getMinimumSize(JComponent c) { Dimension d = getPreferredSize(c); d.setSize(tabBounds.width+specialTabSize.width*3, tabBar.getTabCount() == 0 ? 0 : d.height); return d; } public Dimension getMaximumSize(JComponent c) { checkComponent(c); checkUpdate(); return new Dimension(Integer.MAX_VALUE, tabBounds.height); } // **** event handler protected BasicEventHandler createEventHandler(JTabBar bar) { return new BasicEventHandler(bar, this); } protected BasicEventHandler lookupEventHandler(JTabBar bar) { Object[] listeners = bar.getListeners(MouseListener.class); for (int i = listeners.length - 1; i >= 0; i--) if (listeners[i] instanceof BasicEventHandler) return (BasicEventHandler) listeners[i]; return null; } // **** event handler class protected static class BasicEventHandler implements MouseListener, MouseMotionListener, FocusListener, DragGestureListener, DragSourceListener, DropTargetListener { public static String SELECT_INC = "sel.inc"; public static String SELECT_DEC = "sel.dec"; public static String SELECT_CLOSETAB = "sel.closeTab"; protected JTabBar tabBar; protected FFTabBarUI ui; protected DragSource dragSource = new DragSource(); protected DropTarget dropTarget; protected DragGestureRecognizer dgr; public BasicEventHandler(JTabBar bar, FFTabBarUI ui) { this.tabBar = bar; this.ui = ui; } public void install() { tabBar.addMouseListener(this); tabBar.addMouseMotionListener(this); tabBar.addFocusListener(this); installKeyboardActions(); dgr = dragSource.createDefaultDragGestureRecognizer(tabBar, DnDConstants.ACTION_MOVE, this); dropTarget = new DropTarget(tabBar, DnDConstants.ACTION_MOVE, this); } public void uninstall() { tabBar.removeMouseListener(this); tabBar.removeMouseMotionListener(this); tabBar.removeFocusListener(this); uninstallKeyboardActions(); } public static void loadActionMap(NMLazyActionMap map) { map.put(new Actions(SELECT_INC)); map.put(new Actions(SELECT_DEC)); map.put(new Actions(SELECT_CLOSETAB)); } protected InputMap createInputMapWhenFocused() { InputMap map = new InputMap(); fillInputMap(map); return map; } protected void fillInputMap(InputMap map) { map.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), SELECT_INC); map.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), SELECT_INC); map.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), SELECT_DEC); map.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), SELECT_DEC); map.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, KeyEvent.CTRL_DOWN_MASK), SELECT_CLOSETAB); } public void installKeyboardActions() { NMLazyActionMap.installLazyActionMap(UIManager.getDefaults(), tabBar, getClass(), "TabBarActionMapKey"); InputMap im = createInputMapWhenFocused(); SwingUtilities.replaceUIInputMap(tabBar, JComponent.WHEN_FOCUSED, im); } public void uninstallKeyboardActions() { SwingUtilities.replaceUIInputMap(tabBar, JComponent.WHEN_IN_FOCUSED_WINDOW, null); SwingUtilities.replaceUIInputMap(tabBar, JComponent.WHEN_FOCUSED, null); SwingUtilities.replaceUIActionMap(tabBar, null); } public void mouseClicked(MouseEvent e) { if (Platform.isLeftMouseButtonOnly(e)) { int closeButton = ui.getCloseButtonHoverIndex(e.getX(), e.getY()); if (closeButton>=0) { saveCloseTab(tabBar, closeButton); return; } } } public void mouseEntered(MouseEvent e) { // TODO Auto-generated method stub } public void mouseExited(MouseEvent e) { ui.setHoverIndex(-1); ui.setCloseButtonHoverIndex(-1); } private boolean handlePopupTrigger(MouseEvent e) { if (Platform.isPopupTrigger(e)) { int tabIndex = ui.getTabIndexForLocation(e.getX(), e.getY()); if (tabIndex>=0) { tabBar.showContextMenuForTab(e, tabIndex); } return true; } return false; } public void mousePressed(MouseEvent e) { if (handlePopupTrigger(e)) return; if (!tabBar.hasFocus()) tabBar.requestFocus(); int closeButton = ui.getCloseButtonHoverIndex(e.getX(), e.getY()); if (closeButton>=0) { return; } int index = ui.getTabIndexForLocation(e.getX(), e.getY()); if (index>=0) { tabBar.setSelectedIndex(index); } else { switch (index) { case BACKWARDS_TAB: saveSetSelectedIndex(tabBar, tabBar.getSelectedIndex()-1); break; case FORWARD_TAB: saveSetSelectedIndex(tabBar, tabBar.getSelectedIndex()+1); break; case DROP_DOWN_TAB: ui.showDropDownMenu(e); break; } } } public void mouseReleased(MouseEvent e) { if (handlePopupTrigger(e)) return; } public void mouseDragged(MouseEvent e) { // TODO Auto-generated method stub } public void mouseMoved(MouseEvent e) { int index = ui.getTabIndexForLocation(e.getX(), e.getY()); ui.setHoverIndex(index); ui.setCloseButtonHoverIndex(ui.getCloseButtonHoverIndex(e.getX(), e.getY())); } public void focusGained(FocusEvent e) { if (tabBar.isEnabled() && tabBar.getTabCount() > 0) tabBar.repaint(); } public void focusLost(FocusEvent e) { if (tabBar.isEnabled() && tabBar.getTabCount() > 0) tabBar.repaint(); } public static class Actions extends AbstractAction { /** * */ private static final long serialVersionUID = -8696893310959747343L; public Actions(String name) { super(name); } public String getName() { return (String) super.getValue(NAME); } public void actionPerformed(ActionEvent e) { JTabBar<?> c = (JTabBar<?>) e.getSource(); String key = getName(); if (key == SELECT_INC) saveSetSelectedIndex(c, c.getSelectedIndex() + 1); else if (key == SELECT_DEC) saveSetSelectedIndex(c, c.getSelectedIndex() - 1); else if (key == SELECT_CLOSETAB) saveCloseTab(c, c.getSelectedIndex()); } } static void saveSetSelectedIndex(JTabBar<?> c, int selectedIndex) { if (checkIndex(c, selectedIndex)) c.setSelectedIndex(selectedIndex); } static void saveCloseTab(JTabBar<?> c, int index) { if (c.isCloseActionEnabled() && checkIndex(c, index)) c.askRemove(index); } static boolean checkIndex(JTabBar<?> c, int index) { return (index >= 0 && index < c.getTabCount()); } protected transient static Object dragSourceComponent; public void dragGestureRecognized(DragGestureEvent dge) { Point p = dge.getDragOrigin(); if (ui.isInCloseButtonBounds(p.x, p.y)) { return; } if (dge.getComponent() == tabBar && dge.getDragAction() == DnDConstants.ACTION_MOVE) { int tabIndex = tabBar.getSelectedIndex(); if (tabIndex<0) return; Object item = tabBar.getItemAt(tabIndex); Transferable t = new TransferableTab(item, tabIndex); try { dragSourceComponent = tabBar; dragSource.startDrag(dge, DragSource.DefaultMoveDrop, t, this); } catch (InvalidDnDOperationException e) { // e.printStackTrace(); } } } public void dragDropEnd(DragSourceDropEvent dsde) { } public void dragEnter(DragSourceDragEvent dsde) { DragSourceContext context = dsde.getDragSourceContext(); if (validDragSourceEvent(dsde)) { context.setCursor(DragSource.DefaultMoveDrop); } else { context.setCursor(DragSource.DefaultMoveNoDrop); } } protected boolean validDragSourceEvent(DragSourceEvent e) { DragSourceContext context = e.getDragSourceContext(); return (context.getComponent() == tabBar && context.getTransferable() instanceof TransferableTab); } public void dragExit(DragSourceEvent dse) { DragSourceContext context = dse.getDragSourceContext(); context.setCursor(DragSource.DefaultMoveNoDrop); ui.setDropTargetIndex(-1); } protected transient Point p; protected Point screenToTabBar(int x, int y) { if (p == null) p = new Point(); p.setLocation(x, y); SwingUtilities.convertPointFromScreen(p, tabBar); return p; } public void dragOver(DragSourceDragEvent dsde) { } public void dropActionChanged(DragSourceDragEvent dsde) { } public void dragEnter(DropTargetDragEvent dtde) { // TODO Auto-generated method stub } public void dragExit(DropTargetEvent dte) { ui.setDropTargetIndex(-1); } public void dragOver(DropTargetDragEvent dtde) { Point p = dtde.getLocation(); Transferable transfer = dtde.getTransferable(); if (!transfer.isDataFlavorSupported(TransferableTab.tabIndexFlavor)) { int dragOverTab = ui.getTabIndexForLocation(p.x, p.y); if (dragOverTab>=0) { // something unknown is dragged around // store previous selection index // TODO restore index after drop if (ui.dndPreviousSelectionIndex < 0) ui.dndPreviousSelectionIndex = ui.tabBar.getSelectedIndex(); // select tab below cursor ui.tabBar.setSelectedIndex(dragOverTab); } dtde.rejectDrag(); return; } if (!(dropOk(dtde) && transferableOk(transfer)&& actionOk(dtde.getDropAction()) )) { ui.setDropTargetIndex(-1); dtde.rejectDrag(); return; } int index = ui.getTabIndexForLocation(p.x, p.y); ui.setDropTargetIndex(index); dtde.acceptDrag(DnDConstants.ACTION_MOVE); } public void drop(DropTargetDropEvent dtde) { int target = ui.dropTargetIndex; ui.setDropTargetIndex(-1); if (!(target >= 0 && dropOk(dtde) && transferableOk(dtde.getTransferable())&& actionOk(dtde.getDropAction()) )) { dtde.rejectDrop(); return; } dtde.acceptDrop(DnDConstants.ACTION_MOVE); int source; try { source = ((TransferableTab) dtde .getTransferable() .getTransferData(TransferableTab.tabIndexFlavor)) .getTabIndex(); } catch (Exception e) { e.printStackTrace(); dtde.dropComplete(false); return; } tabBar.moveTab(target, source); ui.setHoverIndex(target); dtde.dropComplete(true); } public void dropActionChanged(DropTargetDragEvent dtde) { if (!(dropOk(dtde) && actionOk(dtde.getDropAction()) && transferableOk(dtde.getTransferable()))) dtde.rejectDrag(); else dtde.acceptDrag(DnDConstants.ACTION_MOVE); } private boolean actionOk(int action) { return action == DnDConstants.ACTION_MOVE; } private boolean transferableOk(Transferable t) { return t.isDataFlavorSupported(TransferableTab.tabIndexFlavor); } private boolean dropOk(DropTargetEvent dtde) { DropTargetContext context = dtde.getDropTargetContext(); return context.getComponent() == dragSourceComponent; } } private static class TransferableTab implements Transferable { public static DataFlavor tabIndexFlavor = //DataFlavor.stringFlavor; new DataFlavor(TransferableTab.class, "Tab"); private int tabIndex; private Object tabItem; public TransferableTab(Object tabItem, int tabIndex) { this.tabItem = tabItem; this.tabIndex = tabIndex; } private Transferable getTransferableItem() { if (tabItem instanceof Transferable) return (Transferable) tabItem; return null; } public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { if (tabIndexFlavor.equals(flavor)) return this; Transferable t = getTransferableItem(); if (t != null) return t.getTransferData(flavor); throw new UnsupportedFlavorException(flavor); } public int getTabIndex() { return tabIndex; } public DataFlavor[] getTransferDataFlavors() { List<DataFlavor> supportedDataFlavors = new ArrayList<DataFlavor>(); supportedDataFlavors.add(tabIndexFlavor); Transferable t = getTransferableItem(); if (t != null) Collections.addAll(supportedDataFlavors, t.getTransferDataFlavors()); return supportedDataFlavors.toArray(new DataFlavor[supportedDataFlavors.size()]); } public boolean isDataFlavorSupported(DataFlavor flavor) { DataFlavor[] supported = getTransferDataFlavors(); for (int i=0;i<supported.length;i++) if (supported[i].equals(flavor)) return true; return false; } } }