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}