001    /* ========================================================================
002     * JCommon : a free general purpose class library for the Java(tm) platform
003     * ========================================================================
004     *
005     * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006     * 
007     * Project Info:  http://www.jfree.org/jcommon/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it 
010     * under the terms of the GNU Lesser General Public License as published by 
011     * the Free Software Foundation; either version 2.1 of the License, or 
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but 
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022     * USA.  
023     *
024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025     * in the United States and other countries.]
026     * 
027     * ---------------------
028     * AbstractTabbedUI.java
029     * ---------------------
030     * (C)opyright 2004, by Thomas Morgner and Contributors.
031     *
032     * Original Author:  Thomas Morgner;
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *
035     * $Id: AbstractTabbedUI.java,v 1.9 2005/11/03 09:55:27 mungady Exp $
036     *
037     * Changes
038     * -------------------------
039     * 16-Feb-2004 : Initial version
040     * 07-Jun-2004 : Added standard header (DG);
041     */
042    
043    package org.jfree.ui.tabbedui;
044    
045    import java.awt.BorderLayout;
046    import java.awt.Component;
047    import java.awt.Window;
048    import java.awt.event.ActionEvent;
049    import java.beans.PropertyChangeEvent;
050    import java.beans.PropertyChangeListener;
051    import java.util.ArrayList;
052    
053    import javax.swing.AbstractAction;
054    import javax.swing.Action;
055    import javax.swing.JComponent;
056    import javax.swing.JMenu;
057    import javax.swing.JMenuBar;
058    import javax.swing.JPanel;
059    import javax.swing.JTabbedPane;
060    import javax.swing.SwingConstants;
061    import javax.swing.SwingUtilities;
062    import javax.swing.event.ChangeEvent;
063    import javax.swing.event.ChangeListener;
064    
065    import org.jfree.util.Log;
066    
067    /**
068     * A tabbed GUI. All views on the data are contained in tabs. 
069     *
070     * @author Thomas Morgner
071     */
072    public abstract class AbstractTabbedUI extends JComponent {
073    
074        /** The menu bar property key. */
075        public static final String JMENUBAR_PROPERTY = "jMenuBar";
076        
077        /** The global menu property. */
078        public static final String GLOBAL_MENU_PROPERTY = "globalMenu";
079    
080        /**
081         * An exit action.
082         */
083        protected class ExitAction extends AbstractAction {
084    
085            /**
086             * Defines an <code>Action</code> object with a default
087             * description string and default icon.
088             */
089            public ExitAction() {
090                putValue(NAME, "Exit");
091            }
092    
093            /**
094             * Invoked when an action occurs.
095             *
096             * @param e the event.
097             */
098            public void actionPerformed(final ActionEvent e) {
099                attempExit();
100            }
101    
102        }
103    
104        /**
105         * A tab change handler.
106         */
107        private class TabChangeHandler implements ChangeListener {
108    
109            /** The tabbed pane to which this handler is registered. */
110            private final JTabbedPane pane;
111    
112            /**
113             * Creates a new handler.
114             *
115             * @param pane the pane.
116             */
117            public TabChangeHandler(final JTabbedPane pane) {
118                this.pane = pane;
119            }
120    
121            /**
122             * Invoked when the target of the listener has changed its state.
123             *
124             * @param e a ChangeEvent object
125             */
126            public void stateChanged(final ChangeEvent e) {
127                setSelectedEditor(this.pane.getSelectedIndex());
128            }
129        }
130    
131        /**
132         * A tab enable change listener.
133         */
134        private class TabEnableChangeListener implements PropertyChangeListener {
135            
136            /**
137             * Default constructor.
138             */
139            public TabEnableChangeListener() {
140            }
141    
142            /**
143             * This method gets called when a bound property is changed.
144             *
145             * @param evt A PropertyChangeEvent object describing the event source
146             *            and the property that has changed.
147             */
148            public void propertyChange(final PropertyChangeEvent evt) {
149                if (evt.getPropertyName().equals("enabled") == false) {
150                    Log.debug ("PropertyName");
151                    return;
152                }
153                if (evt.getSource() instanceof RootEditor == false) {
154                    Log.debug ("Source");
155                    return;
156                }
157                final RootEditor editor = (RootEditor) evt.getSource();
158                updateRootEditorEnabled(editor);
159            }
160        }
161    
162        /** The list of root editors. One for each tab. */
163        private ArrayList rootEditors;
164        /** The tabbed pane filling the content area. */
165        private JTabbedPane tabbedPane;
166        /** The index of the currently selected root editor. */
167        private int selectedRootEditor;
168        /** The current toolbar. */
169        private JComponent currentToolbar;
170        /** The container component for the toolbar. */
171        private JPanel toolbarContainer;
172        /** The close action assigned to this UI. */
173        private Action closeAction;
174        /** The current menu bar. */
175        private JMenuBar jMenuBar;
176        /** Whether the UI should build a global menu from all root editors. */
177        private boolean globalMenu;
178    
179        /**
180         * Default constructor.
181         */
182        public AbstractTabbedUI() {
183            this.selectedRootEditor = -1;
184    
185            this.toolbarContainer = new JPanel();
186            this.toolbarContainer.setLayout(new BorderLayout());
187    
188            this.tabbedPane = new JTabbedPane(SwingConstants.BOTTOM);
189            this.tabbedPane.addChangeListener(new TabChangeHandler(this.tabbedPane));
190    
191            this.rootEditors = new ArrayList();
192    
193            setLayout(new BorderLayout());
194            add(this.toolbarContainer, BorderLayout.NORTH);
195            add(this.tabbedPane, BorderLayout.CENTER);
196    
197            this.closeAction = createCloseAction();
198        }
199    
200        /**
201         * Returns the tabbed pane.
202         * 
203         * @return The tabbed pane.
204         */
205        protected JTabbedPane getTabbedPane() {
206            return this.tabbedPane;
207        }
208    
209        /**
210         * Defines whether to use a global unified menu bar, which contains
211         * all menus from all tab-panes or whether to use local menubars.
212         * <p>
213         * From an usability point of view, global menubars should be preferred,
214         * as this way users always see which menus are possibly available and
215         * do not wonder where the menus are disappearing.
216         *
217         * @return true, if global menus should be used, false otherwise.
218         */
219        public boolean isGlobalMenu() {
220            return this.globalMenu;
221        }
222    
223        /**
224         * Sets the global menu flag.
225         * 
226         * @param globalMenu  the flag.
227         */
228        public void setGlobalMenu(final boolean globalMenu) {
229            this.globalMenu = globalMenu;
230            if (isGlobalMenu()) {
231                setJMenuBar(updateGlobalMenubar());
232            }
233            else {
234                if (getRootEditorCount () > 0) {
235                  setJMenuBar(createEditorMenubar(getRootEditor(getSelectedEditor())));
236                }
237            }
238        }
239    
240        /**
241         * Returns the menu bar.
242         * 
243         * @return The menu bar.
244         */
245        public JMenuBar getJMenuBar() {
246            return this.jMenuBar;
247        }
248    
249        /**
250         * Sets the menu bar.
251         * 
252         * @param menuBar  the menu bar.
253         */
254        protected void setJMenuBar(final JMenuBar menuBar) {
255            final JMenuBar oldMenuBar = this.jMenuBar;
256            this.jMenuBar = menuBar;
257            firePropertyChange(JMENUBAR_PROPERTY, oldMenuBar, menuBar);
258        }
259    
260        /**
261         * Creates a close action.
262         * 
263         * @return A close action.
264         */
265        protected Action createCloseAction() {
266            return new ExitAction();
267        }
268    
269        /**
270         * Returns the close action.
271         * 
272         * @return The close action.
273         */
274        public Action getCloseAction() {
275            return this.closeAction;
276        }
277    
278        /**
279         * Returns the prefix menus.
280         *
281         * @return The prefix menus.
282         */
283        protected abstract JMenu[] getPrefixMenus();
284    
285        /**
286         * The postfix menus.
287         *
288         * @return The postfix menus.
289         */
290        protected abstract JMenu[] getPostfixMenus();
291    
292        /**
293         * Adds menus.
294         *
295         * @param menuBar the menu bar
296         * @param customMenus the menus that should be added.
297         */
298        private void addMenus(final JMenuBar menuBar, final JMenu[] customMenus) {
299            for (int i = 0; i < customMenus.length; i++) {
300                menuBar.add(customMenus[i]);
301            }
302        }
303    
304        /**
305         * Updates the global menu bar.
306         * @return the fully initialized menu bar.
307         */
308        private JMenuBar updateGlobalMenubar () {
309          JMenuBar menuBar = getJMenuBar();
310          if (menuBar == null) {
311              menuBar = new JMenuBar();
312          }
313          else {
314              menuBar.removeAll();
315          }
316    
317          addMenus(menuBar, getPrefixMenus());
318          for (int i = 0; i < this.rootEditors.size(); i++)
319          {
320              final RootEditor editor = (RootEditor) this.rootEditors.get(i);
321              addMenus(menuBar, editor.getMenus());
322          }
323          addMenus(menuBar, getPostfixMenus());
324          return menuBar;
325        }
326    
327        /**
328         * Creates a menu bar.
329         *
330         * @param root
331         * @return A menu bar.
332         */
333        private JMenuBar createEditorMenubar(final RootEditor root) {
334    
335            JMenuBar menuBar = getJMenuBar();
336            if (menuBar == null) {
337                menuBar = new JMenuBar();
338            }
339            else {
340                menuBar.removeAll();
341            }
342    
343            addMenus(menuBar, getPrefixMenus());
344            if (isGlobalMenu())
345            {
346                for (int i = 0; i < this.rootEditors.size(); i++)
347                {
348                    final RootEditor editor = (RootEditor) this.rootEditors.get(i);
349                    addMenus(menuBar, editor.getMenus());
350                }
351            }
352            else
353            {
354                addMenus(menuBar, root.getMenus());
355            }
356            addMenus(menuBar, getPostfixMenus());
357            return menuBar;
358        }
359    
360        /**
361         * Adds a root editor.
362         *
363         * @param rootPanel the root panel.
364         */
365        public void addRootEditor(final RootEditor rootPanel) {
366            this.rootEditors.add(rootPanel);
367            this.tabbedPane.add(rootPanel.getEditorName(), rootPanel.getMainPanel());
368            rootPanel.addPropertyChangeListener("enabled", new TabEnableChangeListener());
369            updateRootEditorEnabled(rootPanel);
370            if (getRootEditorCount () == 1) {
371                setSelectedEditor(0);
372            }
373            else if (isGlobalMenu()) {
374                setJMenuBar(updateGlobalMenubar());
375            }
376        }
377    
378        /**
379         * Returns the number of root editors.
380         * 
381         * @return The count.
382         */
383        public int getRootEditorCount () {
384            return this.rootEditors.size();
385        }
386    
387        /**
388         * Returns the specified editor.
389         * 
390         * @param pos  the position index.
391         *
392         * @return The editor at the given position.
393         */
394        public RootEditor getRootEditor(final int pos) {
395            return (RootEditor) this.rootEditors.get(pos);
396        }
397    
398        /**
399         * Returns the selected editor.
400         * 
401         * @return The selected editor.
402         */
403        public int getSelectedEditor() {
404            return this.selectedRootEditor;
405        }
406    
407        /**
408         * Sets the selected editor.
409         *
410         * @param selectedEditor the selected editor.
411         */
412        public void setSelectedEditor(final int selectedEditor) {
413            final int oldEditor = this.selectedRootEditor;
414            if (oldEditor == selectedEditor) {
415                // no change - so nothing to do!
416                return;
417            }
418            this.selectedRootEditor = selectedEditor;
419            // make sure that only the selected editor is active.
420            // all other editors will be disabled, if needed and
421            // not touched if they are already in the correct state
422    
423            for (int i = 0; i < this.rootEditors.size(); i++) {
424                final boolean shouldBeActive = (i == selectedEditor);
425                final RootEditor container =
426                    (RootEditor) this.rootEditors.get(i);
427                if (container.isActive() && (shouldBeActive == false)) {
428                    container.setActive(false);
429                }
430            }
431    
432            if (this.currentToolbar != null) {
433                closeToolbar();
434                this.toolbarContainer.removeAll();
435                this.currentToolbar = null;
436            }
437    
438            for (int i = 0; i < this.rootEditors.size(); i++) {
439                final boolean shouldBeActive = (i == selectedEditor);
440                final RootEditor container =
441                    (RootEditor) this.rootEditors.get(i);
442                if ((container.isActive() == false) && (shouldBeActive == true)) {
443                    container.setActive(true);
444                    setJMenuBar(createEditorMenubar(container));
445                    this.currentToolbar = container.getToolbar();
446                    if (this.currentToolbar != null) {
447                        this.toolbarContainer.add
448                            (this.currentToolbar, BorderLayout.CENTER);
449                        this.toolbarContainer.setVisible(true);
450                        this.currentToolbar.setVisible(true);
451                    }
452                    else {
453                        this.toolbarContainer.setVisible(false);
454                    }
455    
456                    this.getJMenuBar().repaint();
457                }
458            }
459        }
460    
461        /**
462         * Closes the toolbar.
463         */
464        private void closeToolbar() {
465            if (this.currentToolbar != null) {
466                if (this.currentToolbar.getParent() != this.toolbarContainer) {
467                    // ha!, the toolbar is floating ...
468                    // Log.debug (currentToolbar.getParent());
469                    final Window w = SwingUtilities.windowForComponent(this.currentToolbar);
470                    if (w != null) {
471                        w.setVisible(false);
472                        w.dispose();
473                    }
474                }
475                this.currentToolbar.setVisible(false);
476            }
477        }
478    
479        /**
480         * Attempts to exit.
481         */
482        protected abstract void attempExit();
483    
484        /**
485         * Update handler for the enable state of the root editor.
486         * 
487         * @param editor  the editor.
488         */
489        protected void updateRootEditorEnabled(final RootEditor editor) {
490    
491            final boolean enabled = editor.isEnabled();
492            for (int i = 0; i < this.tabbedPane.getTabCount(); i++) {
493                final Component tab = this.tabbedPane.getComponentAt(i);
494                if (tab == editor.getMainPanel()) {
495                    this.tabbedPane.setEnabledAt(i, enabled);
496                    return;
497                }
498            }
499        }
500    }