/* ======================================================================== * JCommon : a free general purpose class library for the Java(tm) platform * ======================================================================== * * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. * * Project Info: http://www.jfree.org/jcommon/index.html * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Java is a trademark or registered trademark of Sun Microsystems, Inc. * in the United States and other countries.] * * --------------------- * AbstractTabbedUI.java * --------------------- * (C)opyright 2004, by Thomas Morgner and Contributors. * * Original Author: Thomas Morgner; * Contributor(s): David Gilbert (for Object Refinery Limited); * * $Id: AbstractTabbedUI.java,v 1.10 2007/11/02 17:50:37 taqua Exp $ * * Changes * ------------------------- * 16-Feb-2004 : Initial version * 07-Jun-2004 : Added standard header (DG); */ package org.jfree.ui.tabbedui; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Window; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.jfree.util.Log; /** * A tabbed GUI. All views on the data are contained in tabs. * * @author Thomas Morgner */ public abstract class AbstractTabbedUI extends JComponent { /** The menu bar property key. */ public static final String JMENUBAR_PROPERTY = "jMenuBar"; /** The global menu property. */ public static final String GLOBAL_MENU_PROPERTY = "globalMenu"; /** * An exit action. */ protected class ExitAction extends AbstractAction { /** * Defines an <code>Action</code> object with a default * description string and default icon. */ public ExitAction() { putValue(NAME, "Exit"); } /** * Invoked when an action occurs. * * @param e the event. */ public void actionPerformed(final ActionEvent e) { attempExit(); } } /** * A tab change handler. */ private class TabChangeHandler implements ChangeListener { /** The tabbed pane to which this handler is registered. */ private final JTabbedPane pane; /** * Creates a new handler. * * @param pane the pane. */ public TabChangeHandler(final JTabbedPane pane) { this.pane = pane; } /** * Invoked when the target of the listener has changed its state. * * @param e a ChangeEvent object */ public void stateChanged(final ChangeEvent e) { setSelectedEditor(this.pane.getSelectedIndex()); } } /** * A tab enable change listener. */ private class TabEnableChangeListener implements PropertyChangeListener { /** * Default constructor. */ public TabEnableChangeListener() { } /** * This method gets called when a bound property is changed. * * @param evt A PropertyChangeEvent object describing the event source * and the property that has changed. */ public void propertyChange(final PropertyChangeEvent evt) { if (evt.getPropertyName().equals("enabled") == false) { Log.debug ("PropertyName"); return; } if (evt.getSource() instanceof RootEditor == false) { Log.debug ("Source"); return; } final RootEditor editor = (RootEditor) evt.getSource(); updateRootEditorEnabled(editor); } } /** The list of root editors. One for each tab. */ private ArrayList rootEditors; /** The tabbed pane filling the content area. */ private JTabbedPane tabbedPane; /** The index of the currently selected root editor. */ private int selectedRootEditor; /** The current toolbar. */ private JComponent currentToolbar; /** The container component for the toolbar. */ private JPanel toolbarContainer; /** The close action assigned to this UI. */ private Action closeAction; /** The current menu bar. */ private JMenuBar jMenuBar; /** Whether the UI should build a global menu from all root editors. */ private boolean globalMenu; /** * Default constructor. */ public AbstractTabbedUI() { this.selectedRootEditor = -1; this.toolbarContainer = new JPanel(); this.toolbarContainer.setLayout(new BorderLayout()); this.tabbedPane = new JTabbedPane(SwingConstants.BOTTOM); this.tabbedPane.addChangeListener(new TabChangeHandler(this.tabbedPane)); this.rootEditors = new ArrayList(); setLayout(new BorderLayout()); add(this.toolbarContainer, BorderLayout.NORTH); add(this.tabbedPane, BorderLayout.CENTER); this.closeAction = createCloseAction(); } /** * Returns the tabbed pane. * * @return The tabbed pane. */ protected JTabbedPane getTabbedPane() { return this.tabbedPane; } /** * Defines whether to use a global unified menu bar, which contains * all menus from all tab-panes or whether to use local menubars. * <p> * From an usability point of view, global menubars should be preferred, * as this way users always see which menus are possibly available and * do not wonder where the menus are disappearing. * * @return true, if global menus should be used, false otherwise. */ public boolean isGlobalMenu() { return this.globalMenu; } /** * Sets the global menu flag. * * @param globalMenu the flag. */ public void setGlobalMenu(final boolean globalMenu) { this.globalMenu = globalMenu; if (isGlobalMenu()) { setJMenuBar(updateGlobalMenubar()); } else { if (getRootEditorCount () > 0) { setJMenuBar(createEditorMenubar(getRootEditor(getSelectedEditor()))); } } } /** * Returns the menu bar. * * @return The menu bar. */ public JMenuBar getJMenuBar() { return this.jMenuBar; } /** * Sets the menu bar. * * @param menuBar the menu bar. */ protected void setJMenuBar(final JMenuBar menuBar) { final JMenuBar oldMenuBar = this.jMenuBar; this.jMenuBar = menuBar; firePropertyChange(JMENUBAR_PROPERTY, oldMenuBar, menuBar); } /** * Creates a close action. * * @return A close action. */ protected Action createCloseAction() { return new ExitAction(); } /** * Returns the close action. * * @return The close action. */ public Action getCloseAction() { return this.closeAction; } /** * Returns the prefix menus. * * @return The prefix menus. */ protected abstract JMenu[] getPrefixMenus(); /** * The postfix menus. * * @return The postfix menus. */ protected abstract JMenu[] getPostfixMenus(); /** * Adds menus. * * @param menuBar the menu bar * @param customMenus the menus that should be added. */ private void addMenus(final JMenuBar menuBar, final JMenu[] customMenus) { for (int i = 0; i < customMenus.length; i++) { menuBar.add(customMenus[i]); } } /** * Updates the global menu bar. * @return the fully initialized menu bar. */ private JMenuBar updateGlobalMenubar () { JMenuBar menuBar = getJMenuBar(); if (menuBar == null) { menuBar = new JMenuBar(); } else { menuBar.removeAll(); } addMenus(menuBar, getPrefixMenus()); for (int i = 0; i < this.rootEditors.size(); i++) { final RootEditor editor = (RootEditor) this.rootEditors.get(i); addMenus(menuBar, editor.getMenus()); } addMenus(menuBar, getPostfixMenus()); return menuBar; } /** * Creates a menu bar. * * @param root * @return A menu bar. */ private JMenuBar createEditorMenubar(final RootEditor root) { JMenuBar menuBar = getJMenuBar(); if (menuBar == null) { menuBar = new JMenuBar(); } else { menuBar.removeAll(); } addMenus(menuBar, getPrefixMenus()); if (isGlobalMenu()) { for (int i = 0; i < this.rootEditors.size(); i++) { final RootEditor editor = (RootEditor) this.rootEditors.get(i); addMenus(menuBar, editor.getMenus()); } } else { addMenus(menuBar, root.getMenus()); } addMenus(menuBar, getPostfixMenus()); return menuBar; } /** * Adds a root editor. * * @param rootPanel the root panel. */ public void addRootEditor(final RootEditor rootPanel) { this.rootEditors.add(rootPanel); this.tabbedPane.add(rootPanel.getEditorName(), rootPanel.getMainPanel()); rootPanel.addPropertyChangeListener("enabled", new TabEnableChangeListener()); updateRootEditorEnabled(rootPanel); if (getRootEditorCount () == 1) { setSelectedEditor(0); } else if (isGlobalMenu()) { setJMenuBar(updateGlobalMenubar()); } } /** * Returns the number of root editors. * * @return The count. */ public int getRootEditorCount () { return this.rootEditors.size(); } /** * Returns the specified editor. * * @param pos the position index. * * @return The editor at the given position. */ public RootEditor getRootEditor(final int pos) { return (RootEditor) this.rootEditors.get(pos); } /** * Returns the selected editor. * * @return The selected editor. */ public int getSelectedEditor() { return this.selectedRootEditor; } /** * Sets the selected editor. * * @param selectedEditor the selected editor. */ public void setSelectedEditor(final int selectedEditor) { final int oldEditor = this.selectedRootEditor; if (oldEditor == selectedEditor) { // no change - so nothing to do! return; } this.selectedRootEditor = selectedEditor; // make sure that only the selected editor is active. // all other editors will be disabled, if needed and // not touched if they are already in the correct state for (int i = 0; i < this.rootEditors.size(); i++) { final boolean shouldBeActive = (i == selectedEditor); final RootEditor container = (RootEditor) this.rootEditors.get(i); if (container.isActive() && (shouldBeActive == false)) { container.setActive(false); } } if (this.currentToolbar != null) { closeToolbar(); this.toolbarContainer.removeAll(); this.currentToolbar = null; } for (int i = 0; i < this.rootEditors.size(); i++) { final boolean shouldBeActive = (i == selectedEditor); final RootEditor container = (RootEditor) this.rootEditors.get(i); if ((container.isActive() == false) && (shouldBeActive == true)) { container.setActive(true); setJMenuBar(createEditorMenubar(container)); this.currentToolbar = container.getToolbar(); if (this.currentToolbar != null) { this.toolbarContainer.add (this.currentToolbar, BorderLayout.CENTER); this.toolbarContainer.setVisible(true); this.currentToolbar.setVisible(true); } else { this.toolbarContainer.setVisible(false); } this.getJMenuBar().repaint(); } } } /** * Closes the toolbar. */ private void closeToolbar() { if (this.currentToolbar != null) { if (this.currentToolbar.getParent() != this.toolbarContainer) { // ha!, the toolbar is floating ... // Log.debug (currentToolbar.getParent()); final Window w = SwingUtilities.windowForComponent(this.currentToolbar); if (w != null) { w.setVisible(false); w.dispose(); } } this.currentToolbar.setVisible(false); } } /** * Attempts to exit. */ protected abstract void attempExit(); /** * Update handler for the enable state of the root editor. * * @param editor the editor. */ protected void updateRootEditorEnabled(final RootEditor editor) { final boolean enabled = editor.isEnabled(); for (int i = 0; i < this.tabbedPane.getTabCount(); i++) { final Component tab = this.tabbedPane.getComponentAt(i); if (tab == editor.getMainPanel()) { this.tabbedPane.setEnabledAt(i, enabled); return; } } } }