    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
    043package org.jfree.ui.tabbedui;
    044
    045import java.awt.BorderLayout;
    046import java.awt.Component;
    047import java.awt.Window;
    048import java.awt.event.ActionEvent;
    049import java.beans.PropertyChangeEvent;
    050import java.beans.PropertyChangeListener;
    051import java.util.ArrayList;
    052import javax.swing.AbstractAction;
    053import javax.swing.Action;
    054import javax.swing.JComponent;
    055import javax.swing.JMenu;
    056import javax.swing.JMenuBar;
    057import javax.swing.JPanel;
    058import javax.swing.JTabbedPane;
    059import javax.swing.SwingConstants;
    060import javax.swing.SwingUtilities;
    061import javax.swing.event.ChangeEvent;
    062import javax.swing.event.ChangeListener;
    063
    064import 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 */
    071public 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}