001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 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 >= 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}