/*
* Created on 29-Aug-2008
* Copyright (C) 2008 by Andrea Vacondio.
*
* This program 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.
* This program 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 this program;
* if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.pdfsam.guiclient.commons.panels;
import java.awt.Color;
import java.awt.Component;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JTabbedPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.EventListenerList;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.plaf.metal.MetalTabbedPaneUI;
import org.pdfsam.guiclient.commons.business.listeners.CloseableTabbedPaneListener;
/**
* code snippet found on http://forums.sun.com thanks to "fast_"
*/
public class CloseableTabbedPane extends JTabbedPane implements MouseListener, MouseMotionListener {
/**
*
*/
private static final long serialVersionUID = -1076950974039862520L;
/**
* The <code>EventListenerList</code>.
*/
private EventListenerList listenerList = null;
/**
* The viewport of the scrolled tabs.
*/
private JViewport headerViewport = null;
/**
*
* The normal closeicon.
*/
private Icon normalCloseIcon = null;
/**
* The closeicon when the mouse is over.
*/
private Icon hooverCloseIcon = null;
/**
* The closeicon when the mouse is pressed.
*/
private Icon pressedCloseIcon = null;
/**
* Creates a new instance of <code>CloseableTabbedPane</code>
*/
public CloseableTabbedPane() {
super();
init(SwingUtilities.LEFT);
}
/**
* Creates a new instance of <code>CloseableTabbedPane</code>
*
* @param horizontalTextPosition
* the horizontal position of the text (e.g.
* SwingUtilities.TRAILING or SwingUtilities.LEFT)
*/
public CloseableTabbedPane(int horizontalTextPosition) {
super();
init(horizontalTextPosition);
}
/**
* Initializes the <code>CloseableTabbedPane</code>
*
* @param horizontalTextPosition
* the horizontal position of the text (e.g.
* SwingUtilities.TRAILING or SwingUtilities.LEFT)
*/
private void init(int horizontalTextPosition) {
listenerList = new EventListenerList();
addMouseListener(this);
addMouseMotionListener(this);
if (getUI() instanceof MetalTabbedPaneUI)
setUI(new CloseableMetalTabbedPaneUI(horizontalTextPosition));
else
setUI(new CloseableTabbedPaneUI(horizontalTextPosition));
}
/**
* Allows setting own closeicons.
*
* @param normal
* the normal closeicon
* @param hoover
* the closeicon when the mouse is over
* @param pressed
* the closeicon when the mouse is pressed
*/
public void setCloseIcons(Icon normal, Icon hoover, Icon pressed) {
normalCloseIcon = normal;
hooverCloseIcon = hoover;
pressedCloseIcon = pressed;
}
/**
* Adds a <code>Component</code> represented by a title and no icon.
*
* @param title
* the title to be displayed in this tab
*
* @param component
* the component to be displayed when this tab is clicked
*/
public void addTab(String title, Component component) {
addTab(title, component, null);
}
/**
* Adds a <code>Component</code> represented by a title and an icon.
*
* @param title
* the title to be displayed in this tab
* @param component
* the component to be displayed when this tab is clicked
* @param extraIcon
* the icon to be displayed in this tab
*/
public void addTab(String title, Component component, Icon extraIcon) {
addTab(title, component, extraIcon, null);
}
/**
* Adds a <code>Component</code> represented by a title and an icon.
*
* @param title
* the title to be displayed in this tab
* @param component
* the component to be displayed when this tab is clicked
* @param extraIcon
* the icon to be displayed in this tab
* @param tooltip
* tooltip message
*/
public void addTab(String title, Component component, Icon extraIcon, String tooltip) {
boolean doPaintCloseIcon = true;
try {
Object prop = null;
if ((prop = ((JComponent) component).getClientProperty("isClosable")) != null) {
doPaintCloseIcon = ((Boolean) prop).booleanValue();
}
} catch (Exception ignored) {/* Could probably be a ClassCastException */
}
super.addTab(title, doPaintCloseIcon ? new CloseTabIcon(extraIcon) : null, component, tooltip);
if (headerViewport == null) {
Component[] components = getComponents();
for (int i = 0; i < components.length; i++) {
Component c = components[i];
if ("TabbedPane.scrollableViewport".equals(c.getName()))
headerViewport = (JViewport) c;
}
}
}
/**
* Invoked when the mouse button has been clicked (pressed and released) on
*
* a component.
*
* @param e
* the <code>MouseEvent</code>
*/
public void mouseClicked(MouseEvent e) {
processMouseEvents(e);
}
/**
* Invoked when the mouse enters a component.
*
* @param e
* the <code>MouseEvent</code>
*/
public void mouseEntered(MouseEvent e) {
}
/**
* Invoked when the mouse exits a component.
*
* @param e
* the <code>MouseEvent</code>
*/
public void mouseExited(MouseEvent e) {
for (int i = 0; i < getTabCount(); i++) {
CloseTabIcon icon = (CloseTabIcon) getIconAt(i);
if (icon != null)
icon.mouseover = false;
}
repaint();
}
/**
* Invoked when a mouse button has been pressed on a component.
*
* @param e
* the <code>MouseEvent</code>
*/
public void mousePressed(MouseEvent e) {
processMouseEvents(e);
}
/**
*
* Invoked when a mouse button has been released on a component.
*
* @param e
* the <code>MouseEvent</code>
*/
public void mouseReleased(MouseEvent e) {
}
/**
* Invoked when a mouse button is pressed on a component and then dragged.
* <code>MOUSE_DRAGGED</code> events will continue to be delivered to the
* component where the drag originated until the mouse button is released
*
* (regardless of whether the mouse position is within the bounds of the
* component).<br/> <br/> Due to platform-dependent Drag&Drop
* implementations, <code>MOUSE_DRAGGED</code> events may not be delivered
* during a native
*
* Drag&Drop operation.
*
* @param e
* the <code>MouseEvent</code>
*/
public void mouseDragged(MouseEvent e) {
processMouseEvents(e);
}
/**
* Invoked when the mouse cursor has been moved onto a component but no
* buttons have been pushed.
*
* @param e
* the <code>MouseEvent</code>
*/
public void mouseMoved(MouseEvent e) {
processMouseEvents(e);
}
/**
*
* Processes all caught <code>MouseEvent</code>s.
*
* @param e
* the <code>MouseEvent</code>
*/
private void processMouseEvents(MouseEvent e) {
int tabNumber = getUI().tabForCoordinate(this, e.getX(), e.getY());
if (tabNumber < 0)
return;
CloseTabIcon icon = (CloseTabIcon) getIconAt(tabNumber);
if (icon != null) {
Rectangle rect = icon.getBounds();
Point pos = headerViewport == null ?
new Point() : headerViewport.getViewPosition();
Rectangle drawRect = new Rectangle(rect.x - pos.x, rect.y - pos.y, rect.width, rect.height);
if (e.getID() == MouseEvent.MOUSE_PRESSED) {
icon.mousepressed = e.getModifiers() == MouseEvent.BUTTON1_MASK;
repaint(drawRect);
} else if (e.getID() == MouseEvent.MOUSE_MOVED || e.getID() == MouseEvent.MOUSE_DRAGGED ||
e.getID() == MouseEvent.MOUSE_CLICKED) {
pos.x += e.getX();
pos.y += e.getY();
if (rect.contains(pos)) {
if (e.getID() == MouseEvent.MOUSE_CLICKED) {
int selIndex = getSelectedIndex();
if (fireCloseTab(selIndex)) {
if (selIndex > 0) {
// to prevent uncatchable null-pointers
Rectangle rec = getUI().getTabBounds(this, selIndex - 1);
MouseEvent event = new MouseEvent((Component) e.getSource(), e.getID() + 1, System
.currentTimeMillis(),
e.getModifiers(), rec.x, rec.y, e.getClickCount(),
e.isPopupTrigger(), e.getButton());
dispatchEvent(event);
}
// the tab is being closed
// removeTabAt(tabNumber);
remove(selIndex);
} else {
icon.mouseover = false;
icon.mousepressed = false;
repaint(drawRect);
}
} else {
icon.mouseover = true;
icon.mousepressed = e.getModifiers() == MouseEvent.BUTTON1_MASK;
}
} else {
icon.mouseover = false;
}
repaint(drawRect);
}
}
}
/**
* Adds an <code>CloseableTabbedPaneListener</code> to the tabbedpane.
*
* @param l
* the <code>CloseableTabbedPaneListener</code> to be added
*/
public void addCloseableTabbedPaneListener(CloseableTabbedPaneListener l) {
listenerList.add(CloseableTabbedPaneListener.class, l);
}
/**
* Removes an <code>CloseableTabbedPaneListener</code> from the tabbedpane.
*
* @param l
* the listener to be removed
*/
public void removeCloseableTabbedPaneListener(CloseableTabbedPaneListener l) {
listenerList.remove(CloseableTabbedPaneListener.class, l);
}
/**
* Returns an array of all the <code>SearchListener</code>s added to this
*
* <code>SearchPane</code> with addSearchListener().
*
* @return all of the <code>SearchListener</code>s added or an empty array
* if no listeners have been added
*/
public CloseableTabbedPaneListener[] getCloseableTabbedPaneListener() {
return (CloseableTabbedPaneListener[]) listenerList.getListeners(CloseableTabbedPaneListener.class);
}
/**
* Notifies all listeners that have registered interest for notification on
*
* this event type.
*
* @param tabIndexToClose
* the index of the tab which should be closed
* @return true if the tab can be closed, false otherwise
*/
protected boolean fireCloseTab(int tabIndexToClose) {
boolean closeit = true;
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
for (int j = 0; j < listeners.length; j++) {
Object i = listeners[j];
if (i instanceof CloseableTabbedPaneListener) {
if (!((CloseableTabbedPaneListener) i).closeTab(tabIndexToClose)) {
closeit = false;
break;
}
}
}
return closeit;
}
/**
*
* The class which generates the 'X' icon for the tabs. The constructor
* accepts an icon which is extra to the 'X' icon, so you can have tabs like
* in JBuilder. This value is null if no extra icon is required.
*/
class CloseTabIcon implements Icon {
/**
* the x position of the icon
*/
private int x_pos;
/**
* the y position of the icon
*/
private int y_pos;
/**
* the width the icon
*/
private int width;
/**
* the height the icon
*/
private int height;
/**
* the additional fileicon
*/
private Icon fileIcon;
/**
* true whether the mouse is over this icon, false otherwise
*/
private boolean mouseover = false;
/**
* true whether the mouse is pressed on this icon, false otherwise
*/
private boolean mousepressed = false;
/**
* Creates a new instance of <code>CloseTabIcon</code>
*
* @param fileIcon
* the additional fileicon, if there is one set
*/
public CloseTabIcon(Icon fileIcon) {
this.fileIcon = fileIcon;
width = 16;
height = 16;
}
/**
* Draw the icon at the specified location. Icon implementations may use
* the
*
* Component argument to get properties useful for painting, e.g. the
* foreground or background color.
*
* @param c
* the component which the icon belongs to
* @param g
* the graphic object to draw on
*
* @param x
* the upper left point of the icon in the x direction
* @param y
* the upper left point of the icon in the y direction
*/
public void paintIcon(Component c, Graphics g, int x, int y) {
boolean doPaintCloseIcon = true;
try {
// JComponent.putClientProperty("isClosable", new
// Boolean(false));
JTabbedPane tabbedpane = (JTabbedPane) c;
int tabNumber = tabbedpane.getUI().tabForCoordinate(tabbedpane, x, y);
JComponent curPanel = (JComponent) tabbedpane.getComponentAt(tabNumber);
Object prop = null;
if ((prop = curPanel.getClientProperty("isClosable")) != null) {
doPaintCloseIcon = ((Boolean) prop).booleanValue();
}
} catch (Exception ignored) {/*
* Could probably be a
* ClassCastException
*/
}
if (doPaintCloseIcon) {
x_pos = x;
y_pos = y;
int y_p = y + 1;
if (normalCloseIcon != null && !mouseover) {
normalCloseIcon.paintIcon(c, g, x, y_p);
} else if (hooverCloseIcon != null && mouseover && !mousepressed) {
hooverCloseIcon.paintIcon(c, g, x, y_p);
} else if (pressedCloseIcon != null && mousepressed) {
pressedCloseIcon.paintIcon(c, g, x, y_p);
} else {
y_p++;
Color col = g.getColor();
if (mousepressed && mouseover) {
g.setColor(Color.WHITE);
g.fillRect(x + 1, y_p, 12, 13);
}
g.setColor(Color.black);
g.drawLine(x + 1, y_p, x + 12, y_p);
g.drawLine(x + 1, y_p + 13, x + 12, y_p + 13);
g.drawLine(x, y_p + 1, x, y_p + 12);
g.drawLine(x + 13, y_p + 1, x + 13, y_p + 12);
g.drawLine(x + 3, y_p + 3, x + 10, y_p + 10);
if (mouseover)
g.setColor(Color.GRAY);
g.drawLine(x + 3, y_p + 4, x + 9, y_p + 10);
g.drawLine(x + 4, y_p + 3, x + 10, y_p + 9);
g.drawLine(x + 10, y_p + 3, x + 3, y_p + 10);
g.drawLine(x + 10, y_p + 4, x + 4, y_p + 10);
g.drawLine(x + 9, y_p + 3, x + 3, y_p + 9);
g.setColor(col);
if (fileIcon != null) {
fileIcon.paintIcon(c, g, x + width, y_p);
}
}
}
}
/**
*
* Returns the icon's width.
*
* @return an int specifying the fixed width of the icon.
*/
public int getIconWidth() {
return width + (fileIcon != null ? fileIcon.getIconWidth() : 0);
}
/**
* Returns the icon's height.
*
* @return an int specifying the fixed height of the icon.
*/
public int getIconHeight() {
return height;
}
/**
* Gets the bounds of this icon in the form of a <code>Rectangle<code>
* object. The bounds specify this icon's width, height, and location
* relative to its parent.
*
* @return a rectangle indicating this icon's bounds
*/
public Rectangle getBounds() {
return new Rectangle(x_pos, y_pos, width, height);
}
}
/**
* A specific <code>BasicTabbedPaneUI</code>.
*/
class CloseableTabbedPaneUI extends BasicTabbedPaneUI {
/**
* the horizontal position of the text
*/
private int horizontalTextPosition = SwingUtilities.LEFT;
/**
* Creates a new instance of <code>CloseableTabbedPaneUI</code>
*/
public CloseableTabbedPaneUI() {
}
/**
* Creates a new instance of <code>CloseableTabbedPaneUI</code>
*
* @param horizontalTextPosition
* the horizontal position of the text (e.g.
*
* SwingUtilities.TRAILING or SwingUtilities.LEFT)
*/
public CloseableTabbedPaneUI(int horizontalTextPosition) {
this.horizontalTextPosition = horizontalTextPosition;
}
/**
* Layouts the label
*
* @param tabPlacement
* the placement of the tabs
*
* @param metrics
* the font metrics
* @param tabIndex
* the index of the tab
* @param title
* the title of the tab
* @param icon
* the icon of the tab
* @param tabRect
* the tab boundaries
* @param iconRect
* the icon boundaries
*
* @param textRect
* the text boundaries
* @param isSelected
* true whether the tab is selected, false otherwise
*/
protected void layoutLabel(int tabPlacement, FontMetrics metrics,
int tabIndex, String title, Icon icon, Rectangle tabRect, Rectangle iconRect, Rectangle textRect,
boolean isSelected) {
textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
javax.swing.text.View v = getTextViewForTab(tabIndex);
if (v != null) {
tabPane.putClientProperty("html", v);
}
SwingUtilities.layoutCompoundLabel((JComponent) tabPane, metrics, title, icon,
SwingUtilities.CENTER, SwingUtilities.CENTER, SwingUtilities.CENTER,
// SwingUtilities.TRAILING,
horizontalTextPosition, tabRect, iconRect, textRect,
textIconGap + 2);
tabPane.putClientProperty("html", null);
int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
iconRect.x += xNudge;
iconRect.y += yNudge;
textRect.x += xNudge;
textRect.y += yNudge;
}
}
/**
* A specific <code>MetalTabbedPaneUI</code>.
*/
class CloseableMetalTabbedPaneUI extends MetalTabbedPaneUI {
/**
* the horizontal position of the text
*/
private int horizontalTextPosition = SwingUtilities.LEFT;
/**
* Creates a new instance of <code>CloseableMetalTabbedPaneUI</code>
*/
public CloseableMetalTabbedPaneUI() {
}
/**
* Creates a new instance of <code>CloseableMetalTabbedPaneUI</code>
*
* @param horizontalTextPosition
* the horizontal position of the text (e.g.
*
* SwingUtilities.TRAILING or SwingUtilities.LEFT)
*/
public CloseableMetalTabbedPaneUI(int horizontalTextPosition) {
this.horizontalTextPosition = horizontalTextPosition;
}
/**
* Layouts the label
*
* @param tabPlacement
* the placement of the tabs
*
* @param metrics
* the font metrics
* @param tabIndex
* the index of the tab
* @param title
* the title of the tab
* @param icon
* the icon of the tab
* @param tabRect
* the tab boundaries
* @param iconRect
* the icon boundaries
*
* @param textRect
* the text boundaries
* @param isSelected
* true whether the tab is selected, false otherwise
*/
protected void layoutLabel(int tabPlacement, FontMetrics metrics,
int tabIndex, String title, Icon icon, Rectangle tabRect, Rectangle iconRect, Rectangle textRect,
boolean isSelected) {
textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
javax.swing.text.View v = getTextViewForTab(tabIndex);
if (v != null) {
tabPane.putClientProperty("html", v);
}
SwingUtilities.layoutCompoundLabel((JComponent) tabPane, metrics, title, icon,
SwingUtilities.CENTER, SwingUtilities.CENTER, SwingUtilities.CENTER,
// SwingUtilities.TRAILING,
horizontalTextPosition, tabRect, iconRect, textRect,
textIconGap + 2);
tabPane.putClientProperty("html", null);
int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
iconRect.x += xNudge;
iconRect.y += yNudge;
textRect.x += xNudge;
textRect.y += yNudge;
}
}
}