package shoddybattleclient.utils;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.util.ArrayList;
import javax.swing.DefaultSingleSelectionModel;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SingleSelectionModel;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* The standard JTabbedPane does not support dragging tabs to change tab order
* or to remove tabs. As well, JTabbedPane does not support custom components
* in Java 5 or below. Therefore, I decided to reimplement JTabbedPane to
* fulfill what I need.
*
* @author Carlos Fernandez
*/
public class SlideTabbedPane extends JPanel {
private class Tab extends JPanel {
public int index;
public String title;
public String tipText;
public Icon icon;
public Component tabComponent;
public Component component;
public boolean slidable = true;
// The component to render.
private Component m_renderComponent;
// An optimization construct
private Component m_prevComponent;
public int deltaX = 0;
private Insets m_insets = new Insets(4, 6, 3, 6);
public Tab() {
this.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
this.setOpaque(false);
TabListener listener = new TabListener(this);
this.addMouseListener(listener);
this.addMouseMotionListener(listener);
this.addMouseWheelListener(listener);
}
public void update() {
if ((m_renderComponent == null) ||
(m_renderComponent != m_prevComponent)) {
if (tabComponent == null) {
m_renderComponent = new JLabel(title);
m_renderComponent.setForeground(
SlideTabbedPane.this.getForeground());
} else {
m_renderComponent = tabComponent;
}
this.removeAll();
this.add(m_renderComponent);
}
if (tabComponent == null) {
JLabel label = (JLabel)m_renderComponent;
if (!label.getText().equals(title)) {
label.setText(title);
}
label.setIcon(icon);
}
}
@Override
public Insets getInsets() {
return m_insets;
}
@Override
public String getToolTipText() {
return tipText;
}
@Override
public void paintComponent(Graphics g) {
boolean isSelected = (this.index == getSelectedIndex());
Color c = m_panel.getBackground();
if (!isSelected) {
int red = (int)(c.getRed() * 0.85);
int green = (int)(c.getGreen() * 0.85);
int blue = (int)(c.getBlue() * 0.85);
c = new Color(red, green, blue);
}
g.setColor(c);
g.fillRect(0, 0, getWidth(), getHeight());
// Draw top highlight
g.setColor(UIManager.getColor("TabbedPane.highlight"));
g.drawLine(0, 1, getWidth() - 1, 1);
// Draw borders
g.setColor(Color.GRAY);
g.drawLine(0, 0, getWidth() - 1, 0);
g.drawLine(0, 0, 0, getHeight() - 1);
g.drawLine(getWidth() - 1, 0, getWidth() - 1, getHeight() - 1);
}
}
private class TabTopPanel extends JPanel {
private class TabTopLayout implements LayoutManager {
public void addLayoutComponent(String name, Component comp) {}
public void removeLayoutComponent(Component comp) {}
@Override
public void layoutContainer(Container parent) {
int x = 0;
int maxHeight = 0;
for (Tab tab : m_tabs) {
int height = tab.getPreferredSize().height;
if (height > maxHeight) {
maxHeight = height;
}
}
for (Tab tab : m_tabs) {
Dimension d = tab.getPreferredSize();
tab.setBounds(x + tab.deltaX, 0, d.width, maxHeight);
x += d.width;
}
}
@Override
public Dimension minimumLayoutSize(Container parent) {
int width = 0;
int height = 0;
for (Tab tab : m_tabs) {
Dimension d = tab.getPreferredSize();
if (d.height > height) {
height = d.height;
}
width += d.width;
}
return new Dimension(width, height);
}
@Override
public Dimension preferredLayoutSize(Container parent) {
return minimumLayoutSize(parent);
}
}
public TabTopPanel() {
setLayout(new TabTopLayout());
setOpaque(false);
}
public void setSelected(int index) {
Tab t = m_tabs.get(index);
remove(t);
add(t);
}
public void update() {
removeAll();
if (getSelectedIndex() != -1) {
add(m_tabs.get(getSelectedIndex()));
}
for (Tab tab : m_tabs) {
if (getSelectedIndex() == tab.index) {
continue;
}
add(tab);
}
revalidate();
repaint();
}
}
private class ComponentBorder implements Border {
public Insets getBorderInsets(Component c) {
return (Insets)UIManager.get("TabbedPane.contentBorderInsets");
}
public boolean isBorderOpaque() {
return true;
}
public void paintBorder(Component c, Graphics g, int x, int y,
int width, int height) {
int selectedId = getSelectedIndex();
Rectangle selected = (selectedId != -1) ? getBoundsAt(selectedId) :
new Rectangle();
g.setColor(UIManager.getColor("TabbedPane.highlight"));
// Draw highlight
g.drawLine(x, y + 1, selected.x, y + 1);
g.drawLine(selected.x + selected.width - 1, y + 1, width, y + 1);
g.setColor(Color.GRAY);
// Draw Top line
g.drawLine(x, y, selected.x, y);
g.drawLine(selected.x + selected.width - 1, y, width, y);
g.drawLine(x, y, x, y + height);
g.drawLine(x + width - 1, y, x + width - 1, y + height);
g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
}
}
private class TabListener extends MouseAdapter {
Tab m_tab;
Point m_origination = null;
boolean m_startedMoving = false;;
public TabListener(Tab tab) {
m_tab = tab;
}
@Override
public void mouseDragged(MouseEvent e) {
if (m_origination == null) {
return;
}
Point p = e.getLocationOnScreen();
if (!m_startedMoving && (Math.abs(p.x - m_origination.x) < 10)) {
return;
}
m_startedMoving = true;
m_tab.deltaX = p.x - m_origination.x;
Rectangle bounds = getBoundsAt(m_tab.index);
int middleTab = bounds.x + (bounds.width / 2);
// If the middle "crossed over" any other tab, then swap positions
if (m_tab.deltaX != 0) {
int direction = m_tab.deltaX / Math.abs(m_tab.deltaX);
int i = m_tab.index + direction;
int newIndex = -1;
int shift = 0;
while ((i >= 0) && (i < m_tabs.size())) {
if (!m_tabs.get(i).slidable) {
break;
}
bounds = getBoundsAt(i);
int middle = bounds.x + (bounds.width / 2);
if ( ((direction > 0) && (middleTab > middle)) ||
(direction < 0) && (middleTab < middle)) {
newIndex = i;
shift += bounds.width;
} else {
break;
}
i += direction;
}
if (newIndex != -1) {
m_origination.x += direction * shift;
m_tab.deltaX -= direction * shift;
moveTab(m_tab.index, newIndex);
}
}
m_topPanel.revalidate();
// The border needs to be repainted. As soon as I learn how to
// repaint the border only, I'll do that
m_panel.repaint();
}
@Override
public void mousePressed(MouseEvent e) {
setSelectedIndex(m_tab.index);
if ((m_origination == null) && m_tab.slidable) {
m_origination = e.getLocationOnScreen();
}
}
@Override
public void mouseReleased(MouseEvent e) {
m_origination = null;
m_startedMoving = false;
m_tab.deltaX = 0;
m_topPanel.revalidate();
// The border needs to be repainted. As soon as I learn how to
// repaint the border only, I'll do that
m_panel.repaint();
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
int rotation = e.getWheelRotation();
rotation /= Math.abs(rotation); //Set it to +/- one.
int newSelected = getSelectedIndex() + rotation;
if (newSelected >= 0 && newSelected < getTabCount()) {
setSelectedIndex(newSelected);
}
}
}
private class TabChangeListener implements ChangeListener {
public void stateChanged(ChangeEvent e) {
m_topPanel.update();
updateComponent();
fireStateChanged();
}
}
private ArrayList<Tab> m_tabs = new ArrayList<Tab>();
private ArrayList<ChangeListener> m_listeners =
new ArrayList<ChangeListener>();
private SingleSelectionModel m_model = new DefaultSingleSelectionModel();
private TabTopPanel m_topPanel = new TabTopPanel();
private JPanel m_panel = new JPanel();
private ChangeListener m_listener = new TabChangeListener();
public SlideTabbedPane() {
this.setOpaque(false);
this.setLayout(new BorderLayout());
m_panel.setLayout(new BorderLayout());
m_panel.setBorder(new ComponentBorder());
super.add(m_topPanel, BorderLayout.NORTH);
super.add(m_panel);
this.setModel(m_model);
setDefaultColors();
}
private void setDefaultColors() {
setBackground(UIManager.getColor("TabbedPane.background"));
setForeground(UIManager.getColor("TabbedPane.foreground"));
}
public void addChangeListener(ChangeListener l) {
m_listeners.add(l);
}
public void removeChangeListener(ChangeListener l) {
m_listeners.remove(l);
}
public ChangeListener[] getChangeListeners() {
ChangeListener[] listeners = new ChangeListener[m_listeners.size()];
for (int i = 0; i < listeners.length; i++) {
listeners[i] = m_listeners.get(i);
}
return listeners;
}
public void fireStateChanged() {
ChangeEvent evt = new ChangeEvent(this);
for (ChangeListener listener : m_listeners) {
listener.stateChanged(evt);
}
}
public void insertTab(String title, Icon icon, Component component,
String tip, boolean slidable, int index) {
Tab tab = new Tab();
tab.title = title;
tab.tipText = tip;
tab.icon = icon;
tab.component = component;
tab.slidable = slidable;
tab.index = index;
tab.update();
m_tabs.add(index, tab);
m_topPanel.update();
if (m_tabs.size() == 1) {
this.setSelectedIndex(0);
}
}
public void insertTab(String title, Icon icon, Component component,
String tip, int index) {
insertTab(title, icon, component, tip, true, index);
}
public void addTab(String title, Icon icon, Component component,
String tip, boolean slidable) {
insertTab(title, icon, component, tip, slidable, m_tabs.size());
}
public void addTab(String title, Icon icon, Component component,
String tip) {
addTab(title, icon, component, tip, true);
}
public void addTab(String title, Icon icon, Component component) {
addTab(title, icon, component, null);
}
public void addTab(String title, Component component) {
addTab(title, null, component, null);
}
public Component add(Component component) {
addTab(component.getName(), null, component, null);
return component;
}
public Component add(String title, Component component) {
addTab(title, null, component, null);
return component;
}
public Component add(Component component, int index) {
insertTab(component.getName(), null, component, null, index);
return component;
}
public void remove(int index) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
m_tabs.remove(index);
for (int i = index; i < m_tabs.size(); i++) {
m_tabs.get(i).index -= 1;
}
int selected = this.getSelectedIndex();
if (selected == index) {
if (getTabCount() == 0) {
m_model.clearSelection();
} else if(getTabCount() == selected) {
setSelectedIndex(selected - 1);
}
} else if (selected > index) {
this.setSelectedIndex(selected - 1);
}
m_topPanel.update();
m_panel.repaint();
}
public void remove(Component component) {
for (int i = 0; i < m_tabs.size(); i++) {
Component c = m_tabs.get(i).component;
if (c != null && c.equals(component)) {
removeTabAt(i);
}
}
}
public void removeTabAt(int index) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
remove(index);
/* TODO: From the Javadoc: Removes the tab at index. After the
component associated with index is removed, its visibility is reset
to true to ensure it will be visible if added to other containers. */
}
public void removeAll() {
m_tabs.clear();
this.setSelectedIndex(-1);
m_topPanel.update();
}
public int getTabCount() {
return m_tabs.size();
}
public String getTitleAt(int index) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
return m_tabs.get(index).title;
}
public String getToolTipTextAt(int index) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
return m_tabs.get(index).tipText;
}
public Icon getIconAt(int index) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
return m_tabs.get(index).icon;
}
public Component getTabComponentAt(int index) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
return m_tabs.get(index).tabComponent;
}
public Component getComponentAt(int index) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
return m_tabs.get(index).component;
}
public void setTitleAt(int index, String title) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
m_tabs.get(index).title = title;
m_tabs.get(index).update();
m_topPanel.revalidate();
}
public void setToolTipTextAt(int index, String toolTipText) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
m_tabs.get(index).tipText = toolTipText;
}
public void setIconAt(int index, Icon icon) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
m_tabs.get(index).icon = icon;
m_tabs.get(index).update();
m_topPanel.revalidate();
}
public void setTabComponentAt(int index, Component component) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
m_tabs.get(index).tabComponent = component;
m_tabs.get(index).update();
m_topPanel.revalidate();
}
public void setComponentAt(int index, Component component) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
m_tabs.get(index).component = component;
if (this.getSelectedIndex() == index) {
updateComponent();
}
}
public void setTabSlidable(int index, boolean slidable) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
m_tabs.get(index).slidable = slidable;
}
public int indexOfTab(String title) {
for (int i = 0; i < m_tabs.size(); i++) {
if (m_tabs.get(i).title.equals(title)) {
return i;
}
}
return -1;
}
public int indexOfTab(Icon icon) {
for (int i = 0; i < m_tabs.size(); i++) {
if (m_tabs.get(i).icon.equals(icon)) {
return i;
}
}
return -1;
}
public int indexOfTabComponent(Component component) {
for (int i = 0; i < m_tabs.size(); i++) {
if (m_tabs.get(i).tabComponent == null) {
continue;
}
if (m_tabs.get(i).tabComponent.equals(component)) {
return i;
}
}
return -1;
}
public int indexOfComponent(Component component) {
for (int i = 0; i < m_tabs.size(); i++) {
if (m_tabs.get(i).component.equals(component)) {
return i;
}
}
return -1;
}
public void setModel(SingleSelectionModel model) {
m_model.removeChangeListener(m_listener);
m_model = model;
m_model.addChangeListener(m_listener);
}
public SingleSelectionModel getModel() {
return m_model;
}
public void setSelectedIndex(int index) {
if ((index < -1) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
m_model.setSelectedIndex(index);
}
public int getSelectedIndex() {
return m_model.getSelectedIndex();
}
public Component getSelectedComponent() {
return m_tabs.get(getSelectedIndex()).component;
}
public void setSelectedComponent(Component c) {
for (Tab tab : m_tabs) {
if (tab.component == c) {
this.setSelectedIndex(tab.index);
}
}
this.updateComponent();
}
public int indexAtLocation(int x, int y) {
Component c = getComponentAt(x, y);
if (c instanceof Tab) {
return ((Tab)c).index;
}
return -1;
}
public Rectangle getBoundsAt(int index) {
if ((index < 0) || (index >= getTabCount())) {
throw new IndexOutOfBoundsException();
}
Tab tab = m_tabs.get(index);
return tab.getBounds();
}
private void moveTab(int oldIndex, int newIndex) {
int shift = newIndex - oldIndex;
shift /= Math.abs(shift);
Tab temp = m_tabs.get(oldIndex);
temp.index = newIndex;
m_tabs.remove(oldIndex);
m_tabs.add(newIndex, temp);
for (int i = oldIndex; i != newIndex; i += shift) {
m_tabs.get(i).index -= shift;
}
this.setSelectedIndex(temp.index);
}
private void updateTab(int index) {
m_tabs.get(index).update();
}
private void updateComponent() {
int selected = getSelectedIndex();
m_topPanel.repaint();
m_panel.removeAll();
if (getSelectedIndex() == -1) {
return;
}
m_panel.add(m_tabs.get(selected).component);
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
m_panel.revalidate();
m_panel.repaint();
}
});
}
@Override
public Component[] getComponents() {
Component[] components = new Component[getTabCount()];
for (int i = 0; i < m_tabs.size(); i++) {
components[i] = m_tabs.get(i).component;
}
return components;
}
}