001 /* 002 * $Id: JXRootPane.java 3340 2009-05-22 19:25:39Z kschaefe $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 022 package org.jdesktop.swingx; 023 024 import java.awt.BorderLayout; 025 import java.awt.Component; 026 import java.awt.Container; 027 import java.awt.Dimension; 028 import java.awt.Insets; 029 import java.awt.LayoutManager; 030 import java.awt.LayoutManager2; 031 import java.awt.Rectangle; 032 import java.awt.event.ActionEvent; 033 import java.awt.event.KeyEvent; 034 035 import javax.swing.AbstractAction; 036 import javax.swing.Action; 037 import javax.swing.InputMap; 038 import javax.swing.JButton; 039 import javax.swing.JComponent; 040 import javax.swing.JMenuBar; 041 import javax.swing.JRootPane; 042 import javax.swing.JToolBar; 043 import javax.swing.KeyStroke; 044 045 /** 046 * Extends the JRootPane by supporting specific placements for a toolbar and a 047 * status bar. If a status bar exists, then toolbars, menus will be registered 048 * with the status bar. 049 * 050 * @see JXStatusBar 051 * @author Mark Davidson 052 */ 053 public class JXRootPane extends JRootPane { 054 /** 055 * An extended {@code RootLayout} offering support for managing the status 056 * bar. 057 * 058 * @author Karl George Schaefer 059 * @author Jeanette Winzenberg 060 */ 061 protected class XRootLayout extends RootLayout { 062 063 LayoutManager2 delegate; 064 065 /** 066 * The layout manager backing this manager. The delegate is used to 067 * calculate the size when the UI handles the window decorations. 068 * 069 * @param delegate 070 * the backing manager 071 */ 072 public void setLayoutManager(LayoutManager2 delegate) { 073 this.delegate = delegate; 074 } 075 076 private Dimension delegatePreferredLayoutSize(Container parent) { 077 if (delegate == null) 078 return super.preferredLayoutSize(parent); 079 return delegate.preferredLayoutSize(parent); 080 } 081 082 /** 083 * {@inheritDoc} 084 */ 085 @Override 086 public Dimension preferredLayoutSize(Container parent) { 087 Dimension pref = delegatePreferredLayoutSize(parent); 088 if (statusBar != null && statusBar.isVisible()) { 089 Dimension statusPref = statusBar.getPreferredSize(); 090 pref.width = Math.max(pref.width, statusPref.width); 091 pref.height += statusPref.height; 092 } 093 return pref; 094 } 095 096 private Dimension delegateMinimumLayoutSize(Container parent) { 097 if (delegate == null) 098 return super.minimumLayoutSize(parent); 099 return delegate.minimumLayoutSize(parent); 100 } 101 102 /** 103 * {@inheritDoc} 104 */ 105 @Override 106 public Dimension minimumLayoutSize(Container parent) { 107 Dimension pref = delegateMinimumLayoutSize(parent); 108 if (statusBar != null && statusBar.isVisible()) { 109 Dimension statusPref = statusBar.getMinimumSize(); 110 pref.width = Math.max(pref.width, statusPref.width); 111 pref.height += statusPref.height; 112 } 113 return pref; 114 115 } 116 117 private Dimension delegateMaximumLayoutSize(Container parent) { 118 if (delegate == null) 119 120 return super.maximumLayoutSize(parent); 121 return delegate.maximumLayoutSize(parent); 122 } 123 124 /** 125 * {@inheritDoc} 126 */ 127 @Override 128 public Dimension maximumLayoutSize(Container target) { 129 Dimension pref = delegateMaximumLayoutSize(target); 130 if (statusBar != null && statusBar.isVisible()) { 131 Dimension statusPref = statusBar.getMaximumSize(); 132 pref.width = Math.max(pref.width, statusPref.width); 133 // PENDING JW: overflow? 134 pref.height += statusPref.height; 135 } 136 return pref; 137 } 138 139 private void delegateLayoutContainer(Container parent) { 140 if (delegate == null) { 141 super.layoutContainer(parent); 142 } else { 143 delegate.layoutContainer(parent); 144 } 145 } 146 147 /** 148 * {@inheritDoc} 149 */ 150 @Override 151 public void layoutContainer(Container parent) { 152 delegateLayoutContainer(parent); 153 if (statusBar == null || !statusBar.isVisible()) 154 return; 155 Rectangle b = parent.getBounds(); 156 Insets i = getInsets(); 157 int w = b.width - i.right - i.left; 158 int h = b.height - i.top - i.bottom; 159 Dimension statusPref = statusBar.getPreferredSize(); 160 statusBar.setBounds(i.right, b.height - i.bottom 161 - statusPref.height, w, statusPref.height); 162 if (contentPane != null) { 163 Rectangle bounds = contentPane.getBounds(); 164 contentPane.setBounds(bounds.x, bounds.y, bounds.width, 165 bounds.height - statusPref.height); 166 } 167 168 } 169 } 170 171 /** 172 * The current status bar for this root pane. 173 */ 174 protected JXStatusBar statusBar; 175 176 private JToolBar toolBar; 177 178 /** 179 * The button that gets activated when the pane has the focus and 180 * a UI-specific action like pressing the <b>ESC</b> key occurs. 181 */ 182 private JButton cancelButton; 183 184 /** 185 * Creates an extended root pane. 186 */ 187 public JXRootPane() { 188 installKeyboardActions(); 189 } 190 191 /** 192 * {@inheritDoc} 193 */ 194 @Override 195 protected Container createContentPane() { 196 JComponent c = new JXPanel() { 197 /** 198 * {@inheritDoc} 199 */ 200 @Override 201 protected void addImpl(Component comp, Object constraints, int index) { 202 synchronized (getTreeLock()) { 203 super.addImpl(comp, constraints, index); 204 registerStatusBar(comp); 205 } 206 } 207 208 /** 209 * {@inheritDoc} 210 */ 211 @Override 212 public void remove(int index) { 213 synchronized (getTreeLock()) { 214 unregisterStatusBar(getComponent(index)); 215 super.remove(index); 216 } 217 } 218 219 /** 220 * {@inheritDoc} 221 */ 222 @Override 223 public void removeAll() { 224 synchronized (getTreeLock()) { 225 for (Component c : getComponents()) { 226 unregisterStatusBar(c); 227 } 228 229 super.removeAll(); 230 } 231 } 232 }; 233 c.setName(this.getName()+".contentPane"); 234 c.setLayout(new BorderLayout() { 235 /* This BorderLayout subclass maps a null constraint to CENTER. 236 * Although the reference BorderLayout also does this, some VMs 237 * throw an IllegalArgumentException. 238 */ 239 @Override 240 public void addLayoutComponent(Component comp, Object constraints) { 241 if (constraints == null) { 242 constraints = BorderLayout.CENTER; 243 } 244 super.addLayoutComponent(comp, constraints); 245 } 246 }); 247 return c; 248 } 249 250 251 /** 252 * {@inheritDoc} 253 */ 254 @Override 255 public void setLayout(LayoutManager layout) { 256 if (layout instanceof XRootLayout) { 257 // happens if decoration is uninstalled by ui 258 if ((layout != null) && (layout == getLayout())) { 259 ((XRootLayout) layout).setLayoutManager(null); 260 } 261 super.setLayout(layout); 262 } else { 263 if (layout instanceof LayoutManager2) { 264 ((XRootLayout) getLayout()).setLayoutManager((LayoutManager2) layout); 265 if (!isValid()) { 266 invalidate(); 267 } 268 } 269 } 270 } 271 272 /** 273 * {@inheritDoc} 274 */ 275 @Override 276 protected LayoutManager createRootLayout() { 277 return new XRootLayout(); 278 } 279 280 /** 281 * PENDING: move to UI 282 * 283 */ 284 private void installKeyboardActions() { 285 Action escAction = new AbstractAction() { 286 public void actionPerformed(ActionEvent evt) { 287 JButton cancelButton = getCancelButton(); 288 if (cancelButton != null) { 289 cancelButton.doClick(20); 290 } 291 } 292 293 /** 294 * Overridden to hack around #566-swing: 295 * JXRootPane eats escape keystrokes from datepicker popup. 296 * Disable action if there is no cancel button.<p> 297 * 298 * That's basically what RootPaneUI does - only not in 299 * the parameterless isEnabled, but in the one that passes 300 * in the sender (available in UIAction only). We can't test 301 * nor compare against core behaviour, UIAction has 302 * sun package scope. <p> 303 * 304 * 305 */ 306 @Override 307 public boolean isEnabled() { 308 return (cancelButton != null) && (cancelButton.isEnabled()); 309 } 310 }; 311 getActionMap().put("esc-action", escAction); 312 InputMap im = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 313 KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); 314 im.put(key, "esc-action"); 315 } 316 317 private void registerStatusBar(Component comp) { 318 if (statusBar == null || comp == null) { 319 return; 320 } 321 // if (comp instanceof MessageSource) { 322 // MessageSource source = (MessageSource) comp; 323 // source.addMessageListener(statusBar); 324 // } 325 // if (comp instanceof ProgressSource) { 326 // ProgressSource source = (ProgressSource) comp; 327 // source.addProgressListener(statusBar); 328 // } 329 if (comp instanceof Container) { 330 Component[] comps = ((Container) comp).getComponents(); 331 for (int i = 0; i < comps.length; i++) { 332 registerStatusBar(comps[i]); 333 } 334 } 335 } 336 337 private void unregisterStatusBar(Component comp) { 338 if (statusBar == null || comp == null) { 339 return; 340 } 341 // if (comp instanceof MessageSource) { 342 // MessageSource source = (MessageSource) comp; 343 // source.removeMessageListener(statusBar); 344 // } 345 // if (comp instanceof ProgressSource) { 346 // ProgressSource source = (ProgressSource) comp; 347 // source.removeProgressListener(statusBar); 348 // } 349 if (comp instanceof Container) { 350 Component[] comps = ((Container) comp).getComponents(); 351 for (int i = 0; i < comps.length; i++) { 352 unregisterStatusBar(comps[i]); 353 } 354 } 355 } 356 357 /** 358 * Set the status bar for this root pane. Any components held by this root 359 * pane will be registered. If this is replacing an existing status bar then 360 * the existing component will be unregistered from the old status bar. 361 * 362 * @param statusBar 363 * the status bar to use 364 */ 365 public void setStatusBar(JXStatusBar statusBar) { 366 JXStatusBar oldStatusBar = this.statusBar; 367 this.statusBar = statusBar; 368 369 // if (statusBar != null) { 370 // if (handler == null) { 371 // Create the new mouse handler and register the toolbar 372 // and menu components. 373 // handler = new MouseMessagingHandler(this, statusBar); 374 // if (toolBar != null) { 375 // handler.registerListeners(toolBar.getComponents()); 376 // } 377 // if (menuBar != null) { 378 // handler.registerListeners(menuBar.getSubElements()); 379 // } 380 // } else { 381 // handler.setMessageListener(statusBar); 382 // } 383 // } 384 385 Component[] comps = getContentPane().getComponents(); 386 for (int i = 0; i < comps.length; i++) { 387 // Unregister the old status bar. 388 unregisterStatusBar(comps[i]); 389 390 // register the new status bar. 391 registerStatusBar(comps[i]); 392 } 393 if (oldStatusBar != null) { 394 remove(oldStatusBar); 395 } 396 if (statusBar != null) { 397 add(statusBar); 398 } 399 firePropertyChange("statusBar", oldStatusBar, getStatusBar()); 400 } 401 402 /** 403 * Gets the currently installed status bar. 404 * 405 * @return the current status bar 406 */ 407 public JXStatusBar getStatusBar() { 408 return statusBar; 409 } 410 411 // private MouseMessagingHandler handler; 412 413 /** 414 * Set the toolbar bar for this root pane. If a tool bar is currently registered with this 415 * {@code JXRootPane}, then it is removed prior to setting the new tool 416 * bar. If an implementation needs to handle more than one tool bar, a 417 * subclass will need to override the singleton logic used here or manually 418 * add toolbars with {@code getContentPane().add}. 419 * 420 * @param toolBar 421 * the toolbar to register 422 */ 423 public void setToolBar(JToolBar toolBar) { 424 JToolBar oldToolBar = getToolBar(); 425 this.toolBar = toolBar; 426 427 if (oldToolBar != null) { 428 getContentPane().remove(oldToolBar); 429 430 // if (handler != null) { 431 // handler.unregisterListeners(oldToolBar.getComponents()); 432 // } 433 } 434 435 // if (handler != null && this.toolBar != null) { 436 // handler.registerListeners(this.toolBar.getComponents()); 437 // } 438 439 getContentPane().add(BorderLayout.NORTH, this.toolBar); 440 441 //ensure the new toolbar is correctly sized and displayed 442 getContentPane().validate(); 443 444 firePropertyChange("toolBar", oldToolBar, getToolBar()); 445 } 446 447 /** 448 * The currently installed tool bar. 449 * 450 * @return the current tool bar 451 */ 452 public JToolBar getToolBar() { 453 return toolBar; 454 } 455 456 /** 457 * {@inheritDoc} 458 */ 459 @Override 460 public void setJMenuBar(JMenuBar menuBar) { 461 JMenuBar oldMenuBar = this.menuBar; 462 463 super.setJMenuBar(menuBar); 464 465 // if (handler != null && oldMenuBar != null) { 466 // handler.unregisterListeners(oldMenuBar.getSubElements()); 467 // } 468 // 469 // if (handler != null && menuBar != null) { 470 // handler.registerListeners(menuBar.getSubElements()); 471 // } 472 } 473 474 /** 475 * Sets the <code>cancelButton</code> property, 476 * which determines the current default cancel button for this <code>JRootPane</code>. 477 * The cancel button is the button which will be activated 478 * when a UI-defined activation event (typically the <b>ESC</b> key) 479 * occurs in the root pane regardless of whether or not the button 480 * has keyboard focus (unless there is another component within 481 * the root pane which consumes the activation event, 482 * such as a <code>JTextPane</code>). 483 * For default activation to work, the button must be an enabled 484 * descendant of the root pane when activation occurs. 485 * To remove a cancel button from this root pane, set this 486 * property to <code>null</code>. 487 * 488 * @param cancelButton the <code>JButton</code> which is to be the cancel button 489 * @see #getCancelButton() 490 * 491 * @beaninfo 492 * description: The button activated by default for cancel actions in this root pane 493 */ 494 public void setCancelButton(JButton cancelButton) { 495 JButton old = this.cancelButton; 496 497 if (old != cancelButton) { 498 this.cancelButton = cancelButton; 499 500 if (old != null) { 501 old.repaint(); 502 } 503 if (cancelButton != null) { 504 cancelButton.repaint(); 505 } 506 } 507 508 firePropertyChange("cancelButton", old, cancelButton); 509 } 510 511 /** 512 * Returns the value of the <code>cancelButton</code> property. 513 * @return the <code>JButton</code> which is currently the default cancel button 514 * @see #setCancelButton 515 */ 516 public JButton getCancelButton() { 517 return cancelButton; 518 } 519 520 }