package chatty.gui.components.tabs; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; 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.awt.event.MouseWheelListener; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JTabbedPane; import javax.swing.event.ChangeListener; /** * A JPanel that can have one or more Components added to it, and it will show * a JTabbedPane depending on that. */ public class Tabs extends JPanel { public enum TabOrder { /** * Inserts added tabs at the end. */ INSERTION, /** * Inserts new tabs in alphabetic order based on the name of the added * Component. If tabs have been reordered manually, then tabs are * inserted before the first tab whose name would be greater than the * new one. * * <p>Ordering is done ignoring case.</p> */ ALPHABETIC } private final JTabbedPane tabs = new DraggableTabbedPane(); private Component firstComp; private boolean mouseWheelScrolling = true; private boolean mouseWheelScrollingAnywhere = false; private TabOrder order = TabOrder.INSERTION; private JPopupMenu popupMenu; public Tabs() { setLayout(new BorderLayout()); tabs.setOpaque(false); tabs.addMouseWheelListener(new MouseWheelListener() { @Override public void mouseWheelMoved(MouseWheelEvent e) { if (mouseWheelScrolling) { // Only scroll if actually on tabs area int index = tabs.indexAtLocation(e.getX(), e.getY()); if (mouseWheelScrollingAnywhere || index != -1 || isNearLastTab(e.getPoint())) { if (e.getWheelRotation() < 0) { setSelectedPrevious(); } else if (e.getWheelRotation() > 0) { setSelectedNext(); } } } } }); tabs.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { openPopupMenu(e); } @Override public void mouseReleased(MouseEvent e) { openPopupMenu(e); } }); } /** * Open context menu manually instead of relying on the JTabbedPane, so we * can check if it's the currently selected tab (since the context menu will * trigger actions based on the currently selected tab). * * @param e */ private void openPopupMenu(MouseEvent e) { if (!e.isPopupTrigger()) { return; } if (popupMenu == null) { return; } final int index = tabs.indexAtLocation(e.getX(), e.getY()); if (tabs.getSelectedIndex() == index) { popupMenu.show(tabs, e.getX(), e.getY()); } } private boolean isNearLastTab(Point p) { Rectangle bounds = tabs.getBoundsAt(tabs.getTabCount() - 1); bounds.width += 99999; return bounds.contains(p); } public void setMouseWheelScrollingEnabled(boolean enabled) { mouseWheelScrolling = enabled; } public void setMouseWheelScrollingAnywhereEnabled(boolean enabled) { mouseWheelScrollingAnywhere = enabled; } public void setTabPlacement(String location) { tabs.setTabPlacement(getTabPlacementValue(location)); } private int getTabPlacementValue(String location) { switch(location) { case "top": return JTabbedPane.TOP; case "bottom": return JTabbedPane.BOTTOM; case "left": return JTabbedPane.LEFT; case "right": return JTabbedPane.RIGHT; } return JTabbedPane.TOP; } public void setTabLayoutPolicy(String type) { tabs.setTabLayoutPolicy(getTabLayoutPolicyValue(type)); } private int getTabLayoutPolicyValue(String type) { switch(type) { case "wrap": return JTabbedPane.WRAP_TAB_LAYOUT; case "scroll": return JTabbedPane.SCROLL_TAB_LAYOUT; } return JTabbedPane.WRAP_TAB_LAYOUT; } public void setPopupMenu(JPopupMenu menu) { popupMenu = menu; } /** * Adds a Component to the TabbedPane. If there is no component yet, * add it as the first component, otherwise show the TabbedPane and add * both to that. * * @param comp */ public void addTab(Component comp) { if (tabs.getTabCount() == 0) { // Nothing yet in the tab pane if (firstComp == null) { add(comp, BorderLayout.CENTER); firstComp = comp; // Added because it wouldn't show changes after removing the // last element/adding a new one this.validate(); this.repaint(); } else { // Add the JTabbedPane and add the added Component and the // first Component. remove(firstComp); add(tabs, BorderLayout.CENTER); appendTab(firstComp); appendTab(comp); firstComp = null; } } else { appendTab(comp); } } /** * Removes a Component. If after removing the Component there is only one * left, remove the JTabbedPane and add the remaining Component directly * to the JPanel. * * @param comp */ public void removeTab(Component comp) { if (tabs.getTabCount() == 0) { if (firstComp == null || firstComp != comp) { return; } super.remove(firstComp); firstComp = null; // Added these lines because it wasn't showing any change after removing // the last element this.validate(); this.repaint(); return; } tabs.remove(comp); if (tabs.getTabCount() == 1) { // Only one Component remaining, so remove JTabbedPane and // add it directly to the JPanel. if (firstComp != tabs.getSelectedComponent()) { firstComp = tabs.getSelectedComponent(); } tabs.remove(firstComp); add(firstComp,BorderLayout.CENTER); super.remove(tabs); } } /** * Gets the number of Components added. * * @return */ public int getTabCount() { if (tabs.getTabCount() > 0) { return tabs.getTabCount(); } else if (firstComp != null) { return 1; } return 0; } /** * Adds a Component to the end of the JTabbedPane. * * @param comp */ private void appendTab(Component comp) { String title = comp.getName(); int insertAt = findInsertPosition(title); tabs.insertTab(title,null,comp,title,insertAt); } /** * Returns the index this tab should be added at, depending on the current * order setting. * * @param newTabName * @return */ private int findInsertPosition(String newTabName) { if (order == TabOrder.ALPHABETIC) { for (int i = 0; i < tabs.getTabCount(); i++) { if (newTabName.compareToIgnoreCase(tabs.getTitleAt(i)) < 0) { return i; } } } return tabs.getTabCount(); } public void addChangeListener(ChangeListener listener) { tabs.addChangeListener(listener); } /** * Gets the currently selected Component. * * @return */ public Component getSelectedComponent() { if (firstComp != null) { return firstComp; } return tabs.getSelectedComponent(); } public int getSelectedIndex() { return tabs.getSelectedIndex(); } /** * Sets the given component as selected in the JTabbedPane. * * @param c */ public void setSelectedComponent(Component c) { if (tabs.indexOfComponent(c) != -1) { tabs.setSelectedComponent(c); } } /** * Switches to the next TAB, if available, or starts from the beginning. */ public void setSelectedNext() { int index = tabs.getSelectedIndex(); int count = tabs.getTabCount(); if (index+1 < count) { tabs.setSelectedIndex(index+1); } else if (count > 0) { tabs.setSelectedIndex(0); } } public void setSelectedPrevious() { int index = tabs.getSelectedIndex(); int count = tabs.getTabCount(); if (count > 0) { if (index-1 >= 0) { tabs.setSelectedIndex(index-1); } else { tabs.setSelectedIndex(count -1); } } } public void setForegroundAt(int index, Color foreground) { tabs.setForegroundAt(index, foreground); } public void setForegroundForComponent(Component comp, Color foreground) { int index = tabs.indexOfComponent(comp); if (index != -1) { tabs.setForegroundAt(index,foreground); } } public void setTitleForComponent(Component comp, String title) { int index = tabs.indexOfComponent(comp); if (index != -1) { tabs.setTitleAt(index, title); } } public void setOrder(TabOrder order) { this.order = order; } /** * Get all added components relative to the given Component, in a certain * direction. If the given Component is not a tab, then an empty list is * returned. * * @param c The Component used as the center * @param direction -1 for tabs to the left, 1 for tabs to the right and 0 * for tabs in both directions * @return List of components in the given direction, except the given * center one, or empty if no components are found */ public Collection<Component> getComponents(Component c, int direction) { List<Component> result = new ArrayList<>(); int index = tabs.indexOfComponent(c); if (index != -1) { if (direction == 1 || direction == 0) { for (int i = index+1; i < getTabCount(); i++) { result.add(tabs.getComponentAt(i)); } } if (direction == -1 || direction == 0) { for (int i = index-1; i >= 0; i--) { result.add(tabs.getComponentAt(i)); } } } return result; } public Collection<Component> getAllComponents() { List<Component> result = new ArrayList<>(); if (firstComp != null) { result.add(firstComp); } else { for (int i = 0; i < getTabCount(); i++) { result.add(tabs.getComponentAt(i)); } } return result; } }