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 }