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}