/*
* Copyright (c) 2010-2016, Sikuli.org, sikulix.com
* Released under the MIT License.
*
*/
package org.sikuli.ide;
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;
/**
* A JTabbedPane which has a close ('X') icon on each tab.
*
* To add a tab, use the method addTab(String, Component)
*
* To have an extra icon on each tab (e.g. like in JBuilder, showing the file type) use the method
* addTab(String, Component, Icon). Only clicking the 'X' closes the tab.
*/
public class CloseableTabbedPane extends JTabbedPane implements MouseListener,
MouseMotionListener {
/**
* The
* <code>EventListenerList</code>.
*/
private EventListenerList listenerList = null;
/**
* The viewport of the scrolled tabs.
*/
private JViewport headerViewport = null;
/**
* The normal closeicon.
*/
private Icon normalCloseIcon = SikuliIDE.getIconResource("/icons/close-normal.gif");
/**
* The closeicon when the mouse is over.
*/
private Icon hooverCloseIcon = SikuliIDE.getIconResource("/icons/close-hover.gif");
/**
* The closeicon when the mouse is pressed.
*/
private Icon pressedCloseIcon = SikuliIDE.getIconResource("/icons/close-pressed.gif");
private SikuliIDEPopUpMenu popMenuTab = null;
private String lastClosed = null;
public boolean isLastClosedByMove = false;
/**
* 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);
//setUI(new AquaCloseableTabbedPaneUI(horizontalTextPosition));
if (getUI() instanceof MetalTabbedPaneUI) {
setUI(new CloseableMetalTabbedPaneUI(horizontalTextPosition));
}
/*
else
setUI(new CloseableTabbedPaneUI(horizontalTextPosition));
*/
popMenuTab = new SikuliIDEPopUpMenu("POP_TAB", this);
if (!popMenuTab.isValidMenu()) {
popMenuTab = null;
}
}
/**
* 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
*/
@Override
public void addTab(String title, Component component) {
addTab(title, component, null, -1);
}
public void addTab(String title, Component component, int position) {
addTab(title, component, null, position);
}
/**
* 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, int position) {
boolean doPaintCloseIcon = true;
try {
Object prop = null;
if ((prop = ((JComponent) component).
getClientProperty("isClosable")) != null) {
doPaintCloseIcon = (Boolean) prop;
}
} catch (Exception ignored) {/*Could probably be a ClassCastException*/
}
if (position < 0) {
super.addTab(title,
doPaintCloseIcon ? new CloseTabIcon(extraIcon) : null,
component, "RightClick for actions");
} else {
super.insertTab(title,
doPaintCloseIcon ? new CloseTabIcon(extraIcon) : null,
component, "RightClick for actions", position);
}
if (headerViewport == null) {
for (Component c : getComponents()) {
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>
*/
@Override
public void mouseClicked(MouseEvent e) {
processMouseEvents(e);
}
/**
* Invoked when the mouse enters a component.
*
* @param e the <code>MouseEvent</code>
*/
@Override
public void mouseEntered(MouseEvent e) {
}
/**
* Invoked when the mouse exits a component.
*
* @param e the <code>MouseEvent</code>
*/
@Override
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>
*/
@Override
public void mousePressed(MouseEvent e) {
processMouseEvents(e);
}
/**
* Invoked when a mouse button has been released on a component.
*
* @param e the <code>MouseEvent</code>
*/
@Override
public void mouseReleased(MouseEvent e) {
processMouseEvents(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>
*/
@Override
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>
*/
@Override
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;
}
//Debug.log("click tab: " + tabNumber + " cur: " + getSelectedIndex());
if (e.isPopupTrigger()) {
if (popMenuTab != null) {
popMenuTab.doShow(this, e);
}
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() == e.MOUSE_PRESSED) {
icon.mousepressed = e.getModifiers() == e.BUTTON1_MASK;
repaint(drawRect);
} else if (e.getID() == e.MOUSE_MOVED || e.getID() == e.MOUSE_DRAGGED
|| e.getID() == e.MOUSE_CLICKED) {
pos.x += e.getX();
pos.y += e.getY();
if (rect.contains(pos)) {
if (e.getID() == e.MOUSE_CLICKED) {
if (!fireCloseTab(e, tabNumber)) {
icon.mouseover = false;
icon.mousepressed = false;
repaint(drawRect);
}
} else {
icon.mouseover = true;
icon.mousepressed = e.getModifiers() == e.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 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(MouseEvent me, int tabIndexToClose) {
boolean closeit = true;
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
for (Object i : listeners) {
if (i instanceof CloseableTabbedPaneListener) {
if (!((CloseableTabbedPaneListener) i).closeTab(tabIndexToClose)) {
closeit = false;
break;
}
}
}
if (closeit) {
if (tabIndexToClose > 0) {
Rectangle rec = getUI().getTabBounds(this, tabIndexToClose);
MouseEvent event = new MouseEvent((Component) me.getSource(),
me.getID() + 1,
System.currentTimeMillis(),
me.getModifiers(),
rec.x,
rec.y,
me.getClickCount(),
false,
me.getButton());
dispatchEvent(event);
}
remove(tabIndexToClose);
}
return closeit;
}
public void setLastClosed(String bundle) {
lastClosed = bundle;
}
public String getLastClosed() {
return lastClosed;
}
public void resetLastClosed() {
lastClosed = null;
isLastClosedByMove = false;
}
/**
* 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;
}
} 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.GRAY);
/*
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.RED);
}
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;
}
Color darkTabColor = new Color(200, 200, 200);
@Override
protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
super.paintTabBackground(g, tabPlacement, tabIndex, x, y, w, h, isSelected);
if (!isSelected) {
g.setColor(darkTabColor);
g.fillRect(x + 1, y + 1, w - 1, h - 1);
}
}
/**
* 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
*/
@Override
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
*/
@Override
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;
}
}
}