    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    }