001    /*
002     * LAPIS lightweight structured text processing system
003     *
004     * Copyright (C) 1998-2002 Carnegie Mellon University,
005     * Copyright (C) 2003 Massachusetts Institute of Technology.
006     * All rights reserved.
007     *
008     * This library is free software; you can redistribute it
009     * and/or modify it under the terms of the GNU General
010     * Public License as published by the Free Software
011     * Foundation, version 2.
012     *
013     * LAPIS homepage: http://graphics.lcs.mit.edu/lapis/
014     */
015    
016    package lapisx.swing;
017    
018    import java.awt.*;
019    import java.awt.event.*;
020    import java.util.*;
021    import javax.swing.*;
022    
023    
024    /**
025     * A wrapper class for JTabbedPane that hides the tabs when there 
026     * is only one tab.  Otherwise a similar interface to JTabbedPane.
027     */
028    public class DisappearingTabbedPane extends JPanel {
029        public static lapisx.util.Debug debug = lapisx.util.Debug.QUIET;
030    
031        /**
032         * Map from tab component to tab title.
033         */
034        Map children;
035    
036        /**
037         * State pattern.  One of EMPTY, ONE_TAB, or MULTIPLE_TABS.
038         */
039        InternalState state;
040    
041        /**
042         * Panel shown for 0 or 1 tabs.
043         */
044        JPanel singleton;
045    
046        /**
047         * Tabbed pane, shown for more than 1 tab.
048         */
049        JTabbedPane tabs;
050    
051        /**
052         * Layout for this.  Flips between singleton (used in EMPTY and ONE_TAB
053         * states) and tabs (in MULTIPLE_TABS state).
054         */
055        CardLayout cards;
056    
057        
058    
059        /**
060         * Make a DisappearingTabbedPane with same defaults as new JTabbedPane().
061         */
062        public DisappearingTabbedPane () {
063            this (new JTabbedPane ());
064        }
065    
066        /**
067         * Make a DisappearingTabbedPane with given tab placement.  Otherwise,
068         * same defaults as new JTabbedPane(tabPlacement).
069         *
070         * @param tabPlacement  one of SwingConstants.TOP, BOTTOM, LEFT, RIGHT
071         */
072        public DisappearingTabbedPane (int tabPlacement) {
073            this (new JTabbedPane (tabPlacement));
074        }
075    
076        /**
077         * Make a DisappearingTabbedPane that encapsulates the given JTabbedPane.
078         *
079         * @param tabs  JTabbedPane that will be added to the new 
080         *              DisappearingTabbedPane
081         * @requires    don't use the JTabbedPane object after putting it under
082         *              DisappearingTabbedPane's control
083         */
084        public DisappearingTabbedPane (JTabbedPane tabs) {
085            this.tabs = tabs;
086           
087            state = EMPTY;
088            children = new HashMap ();
089    
090            setLayout (cards = new CardLayout ());
091    
092            super.add ("singleton", singleton = new JPanel ());
093            singleton.setLayout (new CardLayout ());
094    
095            super.add ("tabs", tabs);
096        }
097    
098        /**
099         * Add a tab.
100         *
101         * @param title  Title shown on tab
102         * @param comp   Component displayed in tab
103         */
104        public void addTab (String title, Component comp) {
105            debug.println("attempting to add new component to Tabbed pane");
106            if (children.containsKey (comp)){
107                debug.println("component exists, removing");
108                remove (comp);
109            }
110            state.addTab (title, comp);
111            debug.println("added new component. tabs: " + getTabCount());
112        }
113    
114        /** 
115         * returns true if pane already exists in tabbed pane
116         **/
117    
118        public boolean containsComp(Component pane) {
119            return children.containsKey (pane);
120        }
121    
122    
123        /**
124         * Remove a tab.
125         *
126         * @param comp  Component to remove
127         */
128        public void remove (Component comp) {
129            if (!children.containsKey (comp)){
130                debug.println("component exists, removing");
131                return;
132            }
133            state.removeTab (comp);
134            debug.println("component removed. tabs: " + getTabCount());
135    
136        } 
137    
138        /**
139         * Select a tab (bringing it to top).
140         *
141         * @param comp  Component to select
142         */
143        public void setSelectedComponent (Component comp) {
144            state.selectTab (comp);
145        }
146    
147        /**
148         * Get the selected tab (returns null if there is no tab).
149         * 
150         * @return  component contained in the currently selected
151         *          tab
152         */
153        public Component getSelectedComponent () {
154            return state.getSelectedTab ();
155        }
156    
157        /**
158         * Get number of tabs.
159         *
160         * @return  number of tabs in this
161         */
162        public int getTabCount () {
163            return children.size ();
164        }
165    
166    
167        /**
168         * Flip between one-tab and multiple-tab display.
169         *
170         * @param card  "singleton" for singleton tab, "tabs" for tabbed pane
171         */
172        void flipTo (String card) {
173            cards.show (this, card);
174        }
175    
176        /**
177         * State pattern.
178         */
179        static interface InternalState {
180            void addTab (String title, Component comp);
181            void removeTab (Component comp);
182            void selectTab (Component comp);
183            Component getSelectedTab ();
184        }
185        
186        /**
187         * State in which this contains no tabs.
188         */
189        final InternalState EMPTY = new InternalState () {
190                public void addTab (String title, Component comp) {
191                    debug.assertion (children.size () == 0);
192                    debug.assertion (!children.containsKey (comp));
193    
194                    singleton.add (title, comp);
195                    flipTo ("singleton");
196    
197                    children.put (comp, title);
198    
199                    state = ONE_TAB;
200                }
201                public void removeTab (Component comp) {
202                    debug.assertion (false); // shouldn't get here
203                }
204                public void selectTab (Component comp) {
205                    debug.assertion (false); // shouldn't get here
206                }
207                public Component getSelectedTab () {
208                    return null;
209                }
210            };
211    
212        /**
213         * State in which this contains exactly one tab.
214         */
215        final InternalState ONE_TAB = new InternalState () {
216                public void addTab (String title, Component comp) {
217                    debug.assertion (children.size () == 1);
218                    debug.assertion (!children.containsKey (comp));
219                    
220                    Component firstComp = 
221                        (Component) children.keySet ().iterator ().next ();
222                    String firstTitle =
223                        (String) children.get (firstComp);
224    
225                    tabs.addTab (firstTitle, firstComp);
226                    tabs.addTab (title, comp);
227                    flipTo ("tabs");
228    
229                    children.put (comp, title);
230                    debug.assertion (children.size () == 2);
231    
232                    state = MULTIPLE_TABS;
233                }
234                public void removeTab (Component comp) {
235                    debug.assertion (children.containsKey (comp));
236                    children.remove (comp);
237                    singleton.remove (comp);
238                    state = EMPTY;
239                }
240                public void selectTab (Component comp) {
241                    debug.assertion (children.containsKey (comp));
242                    // don't have to do anything, because singleton tab is always
243                    // selected
244                }
245                public Component getSelectedTab () {
246                    return tabs.getSelectedComponent();
247                }
248            };
249                    
250        /**
251         * State in which this contains more than one tab.
252         */
253        final InternalState MULTIPLE_TABS = new InternalState () {
254                public void addTab (String title, Component comp) {
255                    debug.assertion (children.size () > 1);
256                    debug.assertion (!children.containsKey (comp));
257    
258                    tabs.addTab (title, comp);
259                    children.put (comp, title);
260                }
261                public void removeTab (Component comp) {
262                    debug.assertion (children.containsKey (comp));
263                    children.remove (comp);
264                    tabs.remove (comp);
265    
266                    if (children.size () == 1) {
267                        Component firstComp = 
268                            (Component) children.keySet ().iterator ().next ();
269                        String firstTitle =
270                            (String) children.get (firstComp);
271    
272                        singleton.add (firstTitle, firstComp);
273                        flipTo ("singleton");
274    
275                        state = ONE_TAB;
276                    }
277                }
278                public void selectTab (Component comp) {
279                    debug.assertion (children.containsKey (comp));
280                    tabs.setSelectedComponent (comp);
281                } 
282                public Component getSelectedTab () {
283                    return tabs.getSelectedComponent();
284                }               
285            };
286    
287    
288        static int tabsCreated = 0;
289    
290        public static void main (String[] args) {
291            final DisappearingTabbedPane tabs = new DisappearingTabbedPane ();
292            final JToolBar toolbar = new JToolBar ();
293            final JFrame f = new JFrame ("DisappearingTabbedPane"); 
294            Container c = f.getContentPane ();
295            c.add (tabs, BorderLayout.CENTER);
296            c.add (toolbar, BorderLayout.SOUTH);
297    
298            toolbar.add (new AbstractAction ("Add Tab") {
299                    public void actionPerformed (ActionEvent event) {
300                        String title = "Tab " + (tabsCreated++);
301                        final JLabel comp = new JLabel (title);
302                        tabs.addTab (title, comp);
303                        tabs.setSelectedComponent (comp);
304                        toolbar.add (new AbstractAction ("Remove " + title) {
305                            public void actionPerformed (ActionEvent event) {
306                                tabs.remove (comp);
307                                toolbar.remove ((Component) event.getSource ());
308                                f.pack ();
309                            }
310                        });
311                        f.pack ();
312                    }
313                });
314            
315            f.addWindowListener (new WindowAdapter () {
316                public void windowClosing (WindowEvent event) {
317                    System.exit(0);
318                }
319            });
320            f.pack ();
321            f.show ();
322        }
323                
324    }