Source for org.jfree.ui.tabbedui.AbstractTabbedUI

   1: /* ========================================================================
   2:  * JCommon : a free general purpose class library for the Java(tm) platform
   3:  * ========================================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  * 
   7:  * Project Info:  http://www.jfree.org/jcommon/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  * 
  27:  * ---------------------
  28:  * AbstractTabbedUI.java
  29:  * ---------------------
  30:  * (C)opyright 2004, by Thomas Morgner and Contributors.
  31:  *
  32:  * Original Author:  Thomas Morgner;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * $Id: AbstractTabbedUI.java,v 1.9 2005/11/03 09:55:27 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------------------------
  39:  * 16-Feb-2004 : Initial version
  40:  * 07-Jun-2004 : Added standard header (DG);
  41:  */
  42: 
  43: package org.jfree.ui.tabbedui;
  44: 
  45: import java.awt.BorderLayout;
  46: import java.awt.Component;
  47: import java.awt.Window;
  48: import java.awt.event.ActionEvent;
  49: import java.beans.PropertyChangeEvent;
  50: import java.beans.PropertyChangeListener;
  51: import java.util.ArrayList;
  52: 
  53: import javax.swing.AbstractAction;
  54: import javax.swing.Action;
  55: import javax.swing.JComponent;
  56: import javax.swing.JMenu;
  57: import javax.swing.JMenuBar;
  58: import javax.swing.JPanel;
  59: import javax.swing.JTabbedPane;
  60: import javax.swing.SwingConstants;
  61: import javax.swing.SwingUtilities;
  62: import javax.swing.event.ChangeEvent;
  63: import javax.swing.event.ChangeListener;
  64: 
  65: import org.jfree.util.Log;
  66: 
  67: /**
  68:  * A tabbed GUI. All views on the data are contained in tabs. 
  69:  *
  70:  * @author Thomas Morgner
  71:  */
  72: public abstract class AbstractTabbedUI extends JComponent {
  73: 
  74:     /** The menu bar property key. */
  75:     public static final String JMENUBAR_PROPERTY = "jMenuBar";
  76:     
  77:     /** The global menu property. */
  78:     public static final String GLOBAL_MENU_PROPERTY = "globalMenu";
  79: 
  80:     /**
  81:      * An exit action.
  82:      */
  83:     protected class ExitAction extends AbstractAction {
  84: 
  85:         /**
  86:          * Defines an <code>Action</code> object with a default
  87:          * description string and default icon.
  88:          */
  89:         public ExitAction() {
  90:             putValue(NAME, "Exit");
  91:         }
  92: 
  93:         /**
  94:          * Invoked when an action occurs.
  95:          *
  96:          * @param e the event.
  97:          */
  98:         public void actionPerformed(final ActionEvent e) {
  99:             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: }