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 }