001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.ui;
030
031import java.awt.Component;
032import java.util.ArrayList;
033import java.util.List;
034
035import javax.swing.Icon;
036import javax.swing.JPanel;
037import javax.swing.JTabbedPane;
038
039/**
040 * A {@link javax.swing.JTabbedPane} implementation that allows tabbed heavy-weight 
041 * components. When a component is added to a tab it is cached and an associated 
042 * light-weight stand-in component and added instead. When a tab is selected the 
043 * light-weight stand in is removed and it's heavy-weight counter-part is displayed.  
044 * When another tab is selected the reverse happens.
045 * <p>
046 * This was originally written to facilitate the use of {@code Canvas3D} objects in
047 * a {@code JTabbedPane}, but I believe it will work for any heavy-weight component.
048 */
049public class HeavyTabbedPane extends JTabbedPane {
050        private static final long serialVersionUID = -3903797547171213551L;
051        
052        /**
053         * Delay in milliseconds for {@link javax.swing.event.ChangeEvent ChangeEvents}.
054     * This prevents some re-draw issues that popup with the heavy weight components.
055         */
056        protected long heavyWeightDelay = 0;
057        
058        /**
059         * Components, in tab index order, that will be displayed when a
060         * tab is selected.
061         */
062        private List<Component> comps = new ArrayList<>();
063        /**
064         * Components, in tab index order, that will be displayed when a
065         * tab is not selected. These should never actually be visible to the
066         * user.
067         */
068        private List<Component> blanks = new ArrayList<>();
069        
070        /**
071         * Create and return the component to be used when a tab is not visible.
072         * @return Component used for tabs that are not currently selected.
073         */
074        protected Component blank() {
075                return new JPanel();
076        }
077        
078        /**
079         * Set the delay to wait before firing a state change event.
080         *
081         * @param d If &gt;= 0, no delay will be used.
082         */
083        protected void setHeavyWeightDeleay(long d) {
084                if (d < 0) d = 0;
085                heavyWeightDelay = d;
086        }
087        
088        @Override
089        public void insertTab(String title, Icon ico, Component comp, String tip, int idx) {
090                Component blank = blank();
091                blanks.add(idx, blank);
092                comps.add(idx, comp);
093                super.insertTab(title, ico, blank, tip, idx);
094        }
095
096        @Override
097        public int indexOfComponent(Component comp) {
098                // if the tab count does not equal the size of the component caches
099                // this was probably called by something internal. This ensures we
100                // don't return an errant value.
101                if (getTabCount() == blanks.size() && getTabCount() == comps.size()) {
102                        if (comps.contains(comp)) {
103                                return comps.indexOf(comp);
104                        } else if (blanks.contains(comp)) {
105                                return blanks.indexOf(comp);
106                        }
107                }
108                return -1;
109        }
110        
111        @Override
112        public Component getComponentAt(int idx) {
113                // return the actual component, not the blank
114                return comps.get(idx);
115        }
116        
117        @Override
118        public void setComponentAt(int idx, Component comp) {
119                // no need to change the blanks
120                comps.set(idx, comp);
121                super.setComponentAt(idx, comp);
122        }
123        
124        @Override
125        public void setSelectedIndex(int idx) {
126                int prevIdx = getSelectedIndex();
127                super.setSelectedIndex(idx);
128                // show the actual component for the selected index and change
129                // the other to it's blank
130                if (prevIdx != -1 && idx != -1) {
131                        super.setComponentAt(prevIdx, blanks.get(prevIdx));
132                        super.setComponentAt(idx, comps.get(idx));
133                }
134        }
135        
136        @Override
137        public void setSelectedComponent(Component comp) {
138                if (comp == null || comps.indexOf(comp) < 0) {
139                        throw new IllegalArgumentException("Component not found in tabbed pane");
140                }
141                int idx = comps.indexOf(comp);
142                setSelectedIndex(idx);
143        }
144        
145        @Override
146        public void removeTabAt(int idx) {
147                super.removeTabAt(idx);
148                comps.remove(idx);
149                blanks.remove(idx);
150        }
151        
152        @Override
153        public void remove(int idx) {
154                removeTabAt(idx);
155        }
156        
157        @Override
158        public void removeAll() {
159                super.removeAll();
160                comps.clear();
161                blanks.clear();
162        }
163        
164        /**
165         * {@code ChangeEvent} are delayed by the heavy weight delay
166         * milliseconds to aid in the proper rendering of heavy weight components.
167         * @see javax.swing.JTabbedPane#fireStateChanged()
168         */
169        @Override
170        protected void fireStateChanged() {
171                try {
172                        Thread.sleep(heavyWeightDelay);
173                } catch (InterruptedException e) {}
174                super.fireStateChanged();
175        }
176        
177//      public static void main(String[] args) throws Exception {
178//              javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getCrossPlatformLookAndFeelClassName());
179//              JFrame frame = new JFrame("J3DTabbedPane");
180//              frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
181//              final JTabbedPane tabs = new HeavyTabbedPane();
182//              frame.setLayout(new BorderLayout());
183//              frame.add(tabs, BorderLayout.CENTER);
184//              JPanel panel = new JPanel();
185//              panel.setLayout(new BorderLayout());
186//              panel.setName("BluePanel");
187//              panel.add(new JLabel("Actual"), BorderLayout.BEFORE_FIRST_LINE);
188//              panel.setBackground(Color.BLUE);
189//              DisplayImpl display = new DisplayImplJ3D("Blue");
190//              panel.add(display.getComponent(), BorderLayout.CENTER);
191//              tabs.add("BluePanel", panel);
192//              panel = new JPanel();
193//              panel.setLayout(new BorderLayout());
194//              display = new DisplayImplJ3D("Red");
195//              panel.add(display.getComponent(), BorderLayout.CENTER);
196//              panel.setName("RedPanel");
197//              panel.add(new JLabel("Actual"), BorderLayout.BEFORE_FIRST_LINE);
198//              panel.setBackground(Color.RED);
199//              tabs.add("RedPanel", panel);
200//              frame.setSize(400, 600);
201//              frame.setVisible(true);
202//              
203//              tabs.addChangeListener(new ChangeListener() {
204//                      public void stateChanged(ChangeEvent e) {
205//                              System.err.println();
206//                              for (int i=0; i<tabs.getTabCount(); i++) {
207//                                      System.err.println("Tab " + i + " " + tabs.getComponentAt(i).getName());
208//                              }
209//                      }
210//              });
211//      }
212}