001package jmri.util.swing;
002
003import java.awt.Component;
004import java.awt.Container;
005import java.awt.FlowLayout;
006import java.util.ArrayList;
007
008import javax.swing.JPanel;
009import javax.swing.JTabbedPane;
010
011/**
012 Class representing a JPanel that can contain and display N items in a WrapLayout.
013 If N is greater than a "tabMax" parameter, the display is automatically done
014 via a JTabbedPane with tabMax entries on all but the last tab.
015
016@author Bob Jacobsen   (c) 2025
017
018*/
019
020public class OptionallyTabbedPanel extends JPanel {
021
022    /**
023     * @param tabMax the maximum number of items to display before creating a new tab
024     */
025    public OptionallyTabbedPanel(int tabMax) {
026        super();
027        this.tabMax = tabMax;
028        
029        super.add(singlePane);
030        super.add(tabbedPane);
031        
032        currentlyTabbed = false;
033        tabbedPane.setVisible(false);
034        
035        singlePane.setLayout(new WrapLayout(FlowLayout.CENTER, 2, 2));
036    }
037
038    final private int tabMax;  // must be provided in ctor
039    private ArrayList<Component> components = new ArrayList<>(); // to allow re-layout
040    
041    private boolean currentlyTabbed;
042    
043    JPanel singlePane = new JPanel();  // when displaying less than tabMax
044    
045    JTabbedPane tabbedPane = new JTabbedPane(); // when displaying more than tabMax
046    JPanel currentTab;
047
048    @Override
049    public Component add(Component component) {
050
051        int fullCount = components.size();
052        
053        if (!currentlyTabbed && fullCount < tabMax) {
054            // stay in single-pane mode
055            singlePane.add(component);
056            components.add(component);
057        } else {
058            // are we in tabbed mode already, or starting tabbed mode now?
059            if (!currentlyTabbed) {
060                // just starting tabbed mode
061                currentlyTabbed = true;
062                singlePane.setVisible(false);
063                tabbedPane.setVisible(true);        
064                singlePane.removeAll();
065                
066                // move existing contents to a 1st pane in the JTabbedPane
067                currentTab = new JPanel();
068                currentTab.setLayout(new WrapLayout(FlowLayout.CENTER, 2, 2));
069                tabbedPane.add(currentTab, "0-"+(tabMax-1));
070                for (var item : components) {
071                    currentTab.add(item);
072                }
073                
074                // create a new (2nd) tab
075                currentTab = new JPanel();
076                currentTab.setLayout(new WrapLayout(FlowLayout.CENTER, 2, 2));
077                // Add that tab; label will be overridden in updateTabLabel below
078                tabbedPane.add(currentTab, ""+fullCount+"-"+(fullCount+1)); 
079                
080                // and add component to that new tab
081                currentTab.add(component);
082                components.add(component);
083                
084                updateTabLabel();
085                
086            } else {
087                // already in tabbed mode
088                int paneCount = currentTab.getComponentCount();
089                if (paneCount >= tabMax) {
090                    // create a new pane in the tabbed pane, make it current tab
091                    currentTab = new JPanel();
092                    currentTab.setLayout(new WrapLayout(FlowLayout.CENTER, 2, 2));
093                    tabbedPane.add(currentTab, ""+fullCount+"-"+(fullCount+1));
094                }
095                // add the component
096                currentTab.add(component);
097                components.add(component);
098
099                updateTabLabel();
100            }
101        }
102        
103        return component;  // returns component argument
104    }
105
106    // Update the label on the current tab to the proper numbers
107    void updateTabLabel() {
108        int numTab = tabbedPane.getComponentCount()-1; // addressing is zero based
109        int first = numTab*tabMax;
110        int last = numTab*tabMax+currentTab.getComponentCount()-1;
111        if (numTab == 0) last = last + 1;  // first tab includes 0
112        tabbedPane.setTitleAt(numTab, ""+first+" - "+last);
113    }
114    
115    @Override
116    public void removeAll() {
117        // reset to single pane mode
118        currentlyTabbed = false;
119        components = new ArrayList<>();
120        
121        for (var pane : tabbedPane.getComponents()) {
122            if (pane instanceof Container) ((Container)pane).removeAll();
123        }
124        tabbedPane.removeAll();
125        singlePane.removeAll();
126
127    }
128
129}