001/* 002 * $Id: JXRootPane.java 4147 2012-02-01 17:13:24Z 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 022package org.jdesktop.swingx; 023 024import java.awt.BorderLayout; 025import java.awt.Component; 026import java.awt.Container; 027import java.awt.Dimension; 028import java.awt.Insets; 029import java.awt.KeyboardFocusManager; 030import java.awt.LayoutManager; 031import java.awt.LayoutManager2; 032import java.awt.Rectangle; 033import java.awt.event.ActionEvent; 034import java.awt.event.KeyEvent; 035 036import javax.swing.AbstractAction; 037import javax.swing.Action; 038import javax.swing.InputMap; 039import javax.swing.JButton; 040import javax.swing.JComponent; 041import javax.swing.JRootPane; 042import javax.swing.JToolBar; 043import javax.swing.KeyStroke; 044 045import org.jdesktop.beans.JavaBean; 046 047/** 048 * Extends the JRootPane by supporting specific placements for a toolbar and a 049 * status bar. If a status bar exists, then toolbars, menus will be registered 050 * with the status bar. 051 * 052 * @see JXStatusBar 053 * @author Mark Davidson 054 */ 055@JavaBean 056public class JXRootPane extends JRootPane { 057 /** 058 * An extended {@code RootLayout} offering support for managing the status 059 * bar. 060 * 061 * @author Karl George Schaefer 062 * @author Jeanette Winzenberg 063 */ 064 protected class XRootLayout extends RootLayout { 065 066 LayoutManager2 delegate; 067 068 /** 069 * The layout manager backing this manager. The delegate is used to 070 * calculate the size when the UI handles the window decorations. 071 * 072 * @param delegate 073 * the backing manager 074 */ 075 public void setLayoutManager(LayoutManager2 delegate) { 076 this.delegate = delegate; 077 } 078 079 private Dimension delegatePreferredLayoutSize(Container parent) { 080 if (delegate == null) 081 return super.preferredLayoutSize(parent); 082 return delegate.preferredLayoutSize(parent); 083 } 084 085 /** 086 * {@inheritDoc} 087 */ 088 @Override 089 public Dimension preferredLayoutSize(Container parent) { 090 Dimension pref = delegatePreferredLayoutSize(parent); 091 if (statusBar != null && statusBar.isVisible()) { 092 Dimension statusPref = statusBar.getPreferredSize(); 093 pref.width = Math.max(pref.width, statusPref.width); 094 pref.height += statusPref.height; 095 } 096 return pref; 097 } 098 099 private Dimension delegateMinimumLayoutSize(Container parent) { 100 if (delegate == null) 101 return super.minimumLayoutSize(parent); 102 return delegate.minimumLayoutSize(parent); 103 } 104 105 /** 106 * {@inheritDoc} 107 */ 108 @Override 109 public Dimension minimumLayoutSize(Container parent) { 110 Dimension pref = delegateMinimumLayoutSize(parent); 111 if (statusBar != null && statusBar.isVisible()) { 112 Dimension statusPref = statusBar.getMinimumSize(); 113 pref.width = Math.max(pref.width, statusPref.width); 114 pref.height += statusPref.height; 115 } 116 return pref; 117 118 } 119 120 private Dimension delegateMaximumLayoutSize(Container parent) { 121 if (delegate == null) 122 123 return super.maximumLayoutSize(parent); 124 return delegate.maximumLayoutSize(parent); 125 } 126 127 /** 128 * {@inheritDoc} 129 */ 130 @Override 131 public Dimension maximumLayoutSize(Container target) { 132 Dimension pref = delegateMaximumLayoutSize(target); 133 if (statusBar != null && statusBar.isVisible()) { 134 Dimension statusPref = statusBar.getMaximumSize(); 135 pref.width = Math.max(pref.width, statusPref.width); 136 // PENDING JW: overflow? 137 pref.height += statusPref.height; 138 } 139 return pref; 140 } 141 142 private void delegateLayoutContainer(Container parent) { 143 if (delegate == null) { 144 super.layoutContainer(parent); 145 } else { 146 delegate.layoutContainer(parent); 147 } 148 } 149 150 /** 151 * {@inheritDoc} 152 */ 153 @Override 154 public void layoutContainer(Container parent) { 155 delegateLayoutContainer(parent); 156 if (statusBar == null || !statusBar.isVisible()) 157 return; 158 Rectangle b = parent.getBounds(); 159 Insets i = getInsets(); 160 int w = b.width - i.right - i.left; 161// int h = b.height - i.top - i.bottom; 162 Dimension statusPref = statusBar.getPreferredSize(); 163 statusBar.setBounds(i.right, b.height - i.bottom 164 - statusPref.height, w, statusPref.height); 165 if (contentPane != null) { 166 Rectangle bounds = contentPane.getBounds(); 167 contentPane.setBounds(bounds.x, bounds.y, bounds.width, 168 bounds.height - statusPref.height); 169 } 170 171 } 172 } 173 174 /** 175 * The current status bar for this root pane. 176 */ 177 protected JXStatusBar statusBar; 178 179 private JToolBar toolBar; 180 181 /** 182 * The button that gets activated when the pane has the focus and 183 * a UI-specific action like pressing the <b>ESC</b> key occurs. 184 */ 185 private JButton cancelButton; 186 187 /** 188 * Creates an extended root pane. 189 */ 190 public JXRootPane() { 191 installKeyboardActions(); 192 } 193 194 /** 195 * {@inheritDoc} 196 */ 197 @Override 198 protected Container createContentPane() { 199 JComponent c = new JXPanel() { 200 /** 201 * {@inheritDoc} 202 */ 203 @Override 204 protected void addImpl(Component comp, Object constraints, int index) { 205 synchronized (getTreeLock()) { 206 super.addImpl(comp, constraints, index); 207 registerStatusBar(comp); 208 } 209 } 210 211 /** 212 * {@inheritDoc} 213 */ 214 @Override 215 public void remove(int index) { 216 synchronized (getTreeLock()) { 217 unregisterStatusBar(getComponent(index)); 218 super.remove(index); 219 } 220 } 221 222 /** 223 * {@inheritDoc} 224 */ 225 @Override 226 public void removeAll() { 227 synchronized (getTreeLock()) { 228 for (Component c : getComponents()) { 229 unregisterStatusBar(c); 230 } 231 232 super.removeAll(); 233 } 234 } 235 }; 236 c.setName(this.getName()+".contentPane"); 237 c.setLayout(new BorderLayout() { 238 /* This BorderLayout subclass maps a null constraint to CENTER. 239 * Although the reference BorderLayout also does this, some VMs 240 * throw an IllegalArgumentException. 241 */ 242 @Override 243 public void addLayoutComponent(Component comp, Object constraints) { 244 if (constraints == null) { 245 constraints = BorderLayout.CENTER; 246 } 247 super.addLayoutComponent(comp, constraints); 248 } 249 }); 250 return c; 251 } 252 253 254 /** 255 * {@inheritDoc} 256 */ 257 @Override 258 public void setLayout(LayoutManager layout) { 259 if (layout instanceof XRootLayout) { 260 // happens if decoration is uninstalled by ui 261 if ((layout != null) && (layout == getLayout())) { 262 ((XRootLayout) layout).setLayoutManager(null); 263 } 264 super.setLayout(layout); 265 } else { 266 if (layout instanceof LayoutManager2) { 267 ((XRootLayout) getLayout()).setLayoutManager((LayoutManager2) layout); 268 if (!isValid()) { 269 invalidate(); 270 } 271 } 272 } 273 } 274 275 /** 276 * {@inheritDoc} 277 */ 278 @Override 279 protected LayoutManager createRootLayout() { 280 return new XRootLayout(); 281 } 282 283 /** 284 * PENDING: move to UI 285 * 286 */ 287 private void installKeyboardActions() { 288 Action escAction = new AbstractAction() { 289 @Override 290 public void actionPerformed(ActionEvent evt) { 291 JButton cancelButton = getCancelButton(); 292 if (cancelButton != null) { 293 cancelButton.doClick(20); 294 } 295 } 296 297 /** 298 * Overridden to hack around #566-swing: 299 * JXRootPane eats escape keystrokes from datepicker popup. 300 * Disable action if there is no cancel button.<p> 301 * 302 * That's basically what RootPaneUI does - only not in 303 * the parameterless isEnabled, but in the one that passes 304 * in the sender (available in UIAction only). We can't test 305 * nor compare against core behaviour, UIAction has 306 * sun package scope. <p> 307 * 308 * Cont'd (Issue #1358-swingx: popup menus not closed) 309 * The extended hack is inspired by Rob Camick's 310 * <a href="http://tips4java.wordpress.com/2010/10/17/escape-key-and-dialog/"> Blog </a> 311 * and consists in checking if the the rootpane has a popup's actionMap "inserted". 312 * NOTE: this does not work if the popup or any of its children is focusOwner. 313 */ 314 @Override 315 public boolean isEnabled() { 316 Component component = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 317 if (component instanceof JComponent) { 318 Action cancelPopup = ((JComponent)component).getActionMap().get("cancel"); 319 if (cancelPopup != null) return false; 320 } 321 return (cancelButton != null) && (cancelButton.isEnabled()); 322 } 323 }; 324 getActionMap().put("esc-action", escAction); 325 InputMap im = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 326 KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); 327 im.put(key, "esc-action"); 328 } 329 330 private void registerStatusBar(Component comp) { 331 if (statusBar == null || comp == null) { 332 return; 333 } 334 if (comp instanceof Container) { 335 Component[] comps = ((Container) comp).getComponents(); 336 for (int i = 0; i < comps.length; i++) { 337 registerStatusBar(comps[i]); 338 } 339 } 340 } 341 342 private void unregisterStatusBar(Component comp) { 343 if (statusBar == null || comp == null) { 344 return; 345 } 346 if (comp instanceof Container) { 347 Component[] comps = ((Container) comp).getComponents(); 348 for (int i = 0; i < comps.length; i++) { 349 unregisterStatusBar(comps[i]); 350 } 351 } 352 } 353 354 /** 355 * Set the status bar for this root pane. Any components held by this root 356 * pane will be registered. If this is replacing an existing status bar then 357 * the existing component will be unregistered from the old status bar. 358 * 359 * @param statusBar 360 * the status bar to use 361 */ 362 public void setStatusBar(JXStatusBar statusBar) { 363 JXStatusBar oldStatusBar = this.statusBar; 364 this.statusBar = statusBar; 365 366 Component[] comps = getContentPane().getComponents(); 367 for (int i = 0; i < comps.length; i++) { 368 // Unregister the old status bar. 369 unregisterStatusBar(comps[i]); 370 371 // register the new status bar. 372 registerStatusBar(comps[i]); 373 } 374 if (oldStatusBar != null) { 375 remove(oldStatusBar); 376 } 377 if (statusBar != null) { 378 add(statusBar); 379 } 380 firePropertyChange("statusBar", oldStatusBar, getStatusBar()); 381 } 382 383 /** 384 * Gets the currently installed status bar. 385 * 386 * @return the current status bar 387 */ 388 public JXStatusBar getStatusBar() { 389 return statusBar; 390 } 391 392 /** 393 * Set the toolbar bar for this root pane. If a tool bar is currently registered with this 394 * {@code JXRootPane}, then it is removed prior to setting the new tool 395 * bar. If an implementation needs to handle more than one tool bar, a 396 * subclass will need to override the singleton logic used here or manually 397 * add toolbars with {@code getContentPane().add}. 398 * 399 * @param toolBar 400 * the toolbar to register 401 */ 402 public void setToolBar(JToolBar toolBar) { 403 JToolBar oldToolBar = getToolBar(); 404 this.toolBar = toolBar; 405 406 if (oldToolBar != null) { 407 getContentPane().remove(oldToolBar); 408 } 409 410 getContentPane().add(BorderLayout.NORTH, this.toolBar); 411 412 //ensure the new toolbar is correctly sized and displayed 413 getContentPane().validate(); 414 415 firePropertyChange("toolBar", oldToolBar, getToolBar()); 416 } 417 418 /** 419 * The currently installed tool bar. 420 * 421 * @return the current tool bar 422 */ 423 public JToolBar getToolBar() { 424 return toolBar; 425 } 426 427 428 /** 429 * Sets the <code>cancelButton</code> property, 430 * which determines the current default cancel button for this <code>JRootPane</code>. 431 * The cancel button is the button which will be activated 432 * when a UI-defined activation event (typically the <b>ESC</b> key) 433 * occurs in the root pane regardless of whether or not the button 434 * has keyboard focus (unless there is another component within 435 * the root pane which consumes the activation event, 436 * such as a <code>JTextPane</code>). 437 * For default activation to work, the button must be an enabled 438 * descendant of the root pane when activation occurs. 439 * To remove a cancel button from this root pane, set this 440 * property to <code>null</code>. 441 * 442 * @param cancelButton the <code>JButton</code> which is to be the cancel button 443 * @see #getCancelButton() 444 * 445 * @beaninfo 446 * description: The button activated by default for cancel actions in this root pane 447 */ 448 public void setCancelButton(JButton cancelButton) { 449 JButton old = this.cancelButton; 450 451 if (old != cancelButton) { 452 this.cancelButton = cancelButton; 453 454 if (old != null) { 455 old.repaint(); 456 } 457 if (cancelButton != null) { 458 cancelButton.repaint(); 459 } 460 } 461 462 firePropertyChange("cancelButton", old, cancelButton); 463 } 464 465 /** 466 * Returns the value of the <code>cancelButton</code> property. 467 * @return the <code>JButton</code> which is currently the default cancel button 468 * @see #setCancelButton 469 */ 470 public JButton getCancelButton() { 471 return cancelButton; 472 } 473 474}