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