001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2023
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 http://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 * <p>
049 * 
050 * @author <a href="https://www.ssec.wisc.edu/cgi-bin/email_form.cgi?name=Flynn,%20Bruce">Bruce Flynn, SSEC</a>
051 * @version $Id$
052 */
053public class HeavyTabbedPane extends JTabbedPane {
054        private static final long serialVersionUID = -3903797547171213551L;
055        
056        /**
057         * Delay in milliseconds for {@link javax.swing.event.ChangeEvent ChangeEvents}.
058     * This prevents some re-draw issues that popup with the heavy weight components.
059         */
060        protected long heavyWeightDelay = 0;
061        
062        /**
063         * Components, in tab index order, that will be displayed when a
064         * tab is selected.
065         */
066        private List<Component> comps = new ArrayList<>();
067        /**
068         * Components, in tab index order, that will be displayed when a
069         * tab is not selected. These should never actually be visible to the
070         * user.
071         */
072        private List<Component> blanks = new ArrayList<>();
073        
074        /**
075         * Create and return the component to be used when a tab is not visible.
076         * @return Component used for tabs that are not currently selected.
077         */
078        protected Component blank() {
079                return new JPanel();
080        }
081        
082        /**
083         * Set the delay to wait before firing a state change event.
084         *
085         * @param d If &gt;= 0, no delay will be used.
086         */
087        protected void setHeavyWeightDeleay(long d) {
088                if (d < 0) d = 0;
089                heavyWeightDelay = d;
090        }
091        
092        @Override
093        public void insertTab(String title, Icon ico, Component comp, String tip, int idx) {
094                Component blank = blank();
095                blanks.add(idx, blank);
096                comps.add(idx, comp);
097                super.insertTab(title, ico, blank, tip, idx);
098        }
099
100        @Override
101        public int indexOfComponent(Component comp) {
102                // if the tab count does not equal the size of the component caches
103                // this was probably called by something internal. This ensures we
104                // don't return an errant value.
105                if (getTabCount() == blanks.size() && getTabCount() == comps.size()) {
106                        if (comps.contains(comp)) {
107                                return comps.indexOf(comp);
108                        } else if (blanks.contains(comp)) {
109                                return blanks.indexOf(comp);
110                        }
111                }
112                return -1;
113        }
114        
115        @Override
116        public Component getComponentAt(int idx) {
117                // return the actual component, not the blank
118                return comps.get(idx);
119        }
120        
121        @Override
122        public void setComponentAt(int idx, Component comp) {
123                // no need to change the blanks
124                comps.set(idx, comp);
125                super.setComponentAt(idx, comp);
126        }
127        
128        @Override
129        public void setSelectedIndex(int idx) {
130                int prevIdx = getSelectedIndex();
131                super.setSelectedIndex(idx);
132                // show the actual component for the selected index and change
133                // the other to it's blank
134                if (prevIdx != -1 && idx != -1) {
135                        super.setComponentAt(prevIdx, blanks.get(prevIdx));
136                        super.setComponentAt(idx, comps.get(idx));
137                }
138        }
139        
140        @Override
141        public void setSelectedComponent(Component comp) {
142                if (comp == null || comps.indexOf(comp) < 0) {
143                        throw new IllegalArgumentException("Component not found in tabbed pane");
144                }
145                int idx = comps.indexOf(comp);
146                setSelectedIndex(idx);
147        }
148        
149        @Override
150        public void removeTabAt(int idx) {
151                super.removeTabAt(idx);
152                comps.remove(idx);
153                blanks.remove(idx);
154        }
155        
156        @Override
157        public void remove(int idx) {
158                removeTabAt(idx);
159        }
160        
161        @Override
162        public void removeAll() {
163                super.removeAll();
164                comps.clear();
165                blanks.clear();
166        }
167        
168        /**
169         * {@code ChangeEvent} are delayed by the heavy weight delay
170         * milliseconds to aid in the proper rendering of heavy weight components.
171         * @see javax.swing.JTabbedPane#fireStateChanged()
172         */
173        @Override
174        protected void fireStateChanged() {
175                try {
176                        Thread.sleep(heavyWeightDelay);
177                } catch (InterruptedException e) {}
178                super.fireStateChanged();
179        }
180        
181//      public static void main(String[] args) throws Exception {
182//              javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getCrossPlatformLookAndFeelClassName());
183//              JFrame frame = new JFrame("J3DTabbedPane");
184//              frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
185//              final JTabbedPane tabs = new HeavyTabbedPane();
186//              frame.setLayout(new BorderLayout());
187//              frame.add(tabs, BorderLayout.CENTER);
188//              JPanel panel = new JPanel();
189//              panel.setLayout(new BorderLayout());
190//              panel.setName("BluePanel");
191//              panel.add(new JLabel("Actual"), BorderLayout.BEFORE_FIRST_LINE);
192//              panel.setBackground(Color.BLUE);
193//              DisplayImpl display = new DisplayImplJ3D("Blue");
194//              panel.add(display.getComponent(), BorderLayout.CENTER);
195//              tabs.add("BluePanel", panel);
196//              panel = new JPanel();
197//              panel.setLayout(new BorderLayout());
198//              display = new DisplayImplJ3D("Red");
199//              panel.add(display.getComponent(), BorderLayout.CENTER);
200//              panel.setName("RedPanel");
201//              panel.add(new JLabel("Actual"), BorderLayout.BEFORE_FIRST_LINE);
202//              panel.setBackground(Color.RED);
203//              tabs.add("RedPanel", panel);
204//              frame.setSize(400, 600);
205//              frame.setVisible(true);
206//              
207//              tabs.addChangeListener(new ChangeListener() {
208//                      public void stateChanged(ChangeEvent e) {
209//                              System.err.println();
210//                              for (int i=0; i<tabs.getTabCount(); i++) {
211//                                      System.err.println("Tab " + i + " " + tabs.getComponentAt(i).getName());
212//                              }
213//                      }
214//              });
215//      }
216}