001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.swing.tabcontrol;
043:
044: import javax.accessibility.Accessible;
045: import org.netbeans.swing.tabcontrol.event.TabActionEvent;
046: import org.netbeans.swing.tabcontrol.plaf.DefaultTabbedContainerUI;
047:
048: import javax.swing.*;
049: import java.awt.*;
050: import java.awt.event.AWTEventListener;
051: import java.awt.event.ActionListener;
052: import java.awt.event.KeyEvent;
053: import java.awt.event.MouseEvent;
054: import java.util.ArrayList;
055: import java.util.Collections;
056: import java.util.List;
057: import javax.accessibility.AccessibleRole;
058: import javax.swing.JComponent.AccessibleJComponent;
059: import org.openide.util.NbBundle;
060:
061: /**
062: * A tabbed container similar to a JTabbedPane. The tabbed container is a
063: * simple container which contains two components - the tabs displayer, and the
064: * content displayer. The tabs displayer is the thing that actually draws the
065: * tabs; the content displayer contains the components that are being shown.
066: * <p>
067: * The first difference from a JTabbedPane is that it is entirely model driven -
068: * the tab contents are in a data model owned by the displayer. It is not
069: * strictly necessary for the contained components to even be installed in the
070: * AWT hierarchy when not displayed.
071: * <p>
072: * Other differences are more flexibility in the way tabs are displayed by
073: * completely separating the implementation and UI for that from that of
074: * displaying the contents.
075: * <p>
076: * Other interesting aspects are the ability of TabDataModel to deliver complex,
077: * granular events in a single pass with no information loss. Generally, great
078: * effort has been gone to to conflate nothing - that is, adding a component
079: * does not equal selecting it does not equal changing focus, et. cetera,
080: * leaving these decisions more in the hands of the user of the control.
081: * <p>
082: * It is possible to implement a subclass which provides the API of JTabbedPane,
083: * making it a drop-in replacement.
084: * <p>
085: * There are several UI styles a <code>TabbedContainer</code> can have. The type
086: * is passed as an argument to the constructor (support for changing these on the
087: * fly may be added in the future, but such a change is a very heavyweight operation,
088: * and is only desirable to enable use of this component in its various permutations
089: * inside GUI designers). The following styles are supported:
090: * <ul>
091: * <li><b>TYPE_VIEW</b> - These are tabs such as the Explorer window has in NetBeans -
092: * all tabs are always displayed, with the available space equally divided between them.</li>
093: * <li><b>TYPE_EDITOR</b> - Scrolling tabs, coupled with control buttons and mouse wheel
094: * support for scrolling the visible tabs, and a popup which displays a list of tabs.</li>
095: * <li><b>TYPE_SLIDING</b> - Tabs which are displayed as buttons, and may provide a
096: * fade or sliding effect when displayed. For this style, a second click on the selected
097: * tab will hide the selected tab (setting the selection model's selected index to -1).</li></ul>
098: * <p>
099: * <h4>Customizing the appearance of tabs</h4>
100: * Tabs are customized by providing a different UI delegate for the tab displayer component,
101: * via UIManager, in the same manner as any standard Swing component; for <code>TYPE_SLIDING</code>
102: * tabs, simply implementing an alternate UI delegate for the buttons used to represent tabs
103: * is all that is needed.
104: *
105: * <h4>Managing user events on tabs</h4>
106: * When a user clicks a tab, the TabbedContainer will fire an action event to all of its listeners.
107: * This action event will always be an instance of <code>TabActionEvent</code>, which can provide
108: * the index of the tab that was pressed, and the command name of the action that was performed.
109: * A client which wants to handle the event itself (for example, the asking a user if they want
110: * to save data, and possibly vetoing the closing of a tab) may veto (or take full responsibility
111: * for performing) the action by consuming the TabActionEvent.
112: *
113: *<h4>Indication of focus and the "activated" state</h4>
114: * The property <code>active</code> is provided to allow a tabbed container to indicate that it
115: * contains the currently focused component. However, no effort is made to track focus on the
116: * part of the tabbed control - there is too much variability possible (for example, if
117: * a component inside a tab opens a modal dialog, is the tab active or not?). In fact, using
118: * keyboard focus at all to manage the activated state of the component turns out to be a potent
119: * source of hard-to-fix, hard-to-reproduce bugs (especially when components are being added
120: * and removed, or hidden and shown or components which do not reliably produce focus events).
121: * What NetBeans does to solve the problem in a reliable way is the following:
122: * <ol>
123: * <li>Use an AWT even listener to track mouse clicks, and when the mouse is clicked,
124: * <ul>
125: * <li>Find the ancestor that is a tabbed container (if any)</li>
126: * <li>Set the activated state appropriately on it and the previously active container</li>
127: * <li>Ensure that keyboard focus moves into that container</li>
128: * </ul>
129: * <li>Block ctrl-tab style keyboard based focus traversal out of tabbed containers</li>
130: * <li>Provide keyboard actions, with menu items, which will change to a different container,
131: * activating it</li>
132: * </ol>
133: * This may seem complicated, and it probably is overkill for a small application (as is this
134: * tabbed control - it wasn't designed for a small application). It's primary advantage is
135: * that it works.
136: *
137: * @see TabDisplayer
138: * @author Tim Boudreau, Dafe Simonek
139: */
140: public class TabbedContainer extends JComponent implements Accessible {
141: /**
142: * UIManager key for the UI Delegate to be used by tabbed containers.
143: */
144: public static final String TABBED_CONTAINER_UI_CLASS_ID = "TabbedContainerUI"; //NOI18N
145:
146: /**
147: * Creates a "view" style displayer; typically this will have a
148: * fixed width and a single row of tabs which get smaller as more tabs are
149: * added, as seen in NetBeans’ Explorer window.
150: */
151: public static final int TYPE_VIEW = 0;
152: /**
153: * Creates a "editor" style displayer; typically this uses a
154: * scrolling tabs UI for the tab displayer. This is the most scalable of the available
155: * UI styles - it can handle a very large number of tabs with minimal overhead, and
156: * the standard UI implementations of it use a cell-renderer model for painting.
157: */
158: public static final int TYPE_EDITOR = 1;
159:
160: /** Creates a "sliding" view, typically with tabs rendered as
161: * buttons along the left, bottom or right edge, with no scrolling behavior for tabs.
162: * Significant about this UI style is that re-clicking the selected tab will
163: * cause the component displayed to be hidden.
164: * <p>
165: * This is the least scalable of the available UI types, and is intended primarily for
166: * use with a small, fixed set of tabs. By default, the position of the tab displayer
167: * will be determined based on the proximity of the container to the edges of its
168: * parent window. This can be turned off by setting the client property
169: * PROP_MANAGE_TAB_POSITION to Boolean.FALSE.
170: */
171: public static final int TYPE_SLIDING = 2;
172:
173: /**
174: * Creates a Toolbar-style displayer (the style used by the NetBeans Form Editor's
175: * Component Inspector and a few other places in NetBeans).
176: */
177: public static final int TYPE_TOOLBAR = 3;
178:
179: /**
180: * Property fired when <code>setActive()</code> is called
181: */
182: public static final String PROP_ACTIVE = "active"; //NOI18N
183:
184: /** Client property applicable only to TYPE_SLIDING tabs. If set to
185: * Boolean.FALSE, the UI will not automatically try to determine a
186: * correct position for the tab displayer.
187: */
188: public static final String PROP_MANAGE_TAB_POSITION = "manageTabPosition";
189:
190: /**
191: * Action command indicating that the action event signifies the user
192: * clicking the Close button on a tab.
193: */
194: public static final String COMMAND_CLOSE = "close"; //NOI18N
195:
196: /**
197: * Action command indicating that the action event fired signifies the user
198: * selecting a tab
199: */
200: public static final String COMMAND_SELECT = "select"; //NOI18N
201:
202: /** Action command indicating that a popup menu should be shown */
203: public static final String COMMAND_POPUP_REQUEST = "popup"; //NOI18N
204:
205: /** Command indicating a maximize (double-click) request */
206: public static final String COMMAND_MAXIMIZE = "maximize"; //NOI18N
207:
208: public static final String COMMAND_CLOSE_ALL = "closeAll"; //NOI18N
209:
210: public static final String COMMAND_CLOSE_ALL_BUT_THIS = "closeAllButThis"; //NOI18N
211:
212: public static final String COMMAND_ENABLE_AUTO_HIDE = "enableAutoHide"; //NOI18N
213:
214: public static final String COMMAND_DISABLE_AUTO_HIDE = "disableAutoHide"; //NOI18N
215:
216: public static final String COMMAND_TOGGLE_TRANSPARENCY = "toggleTransparency"; //NOI18N
217:
218: //XXX support supressing close buttons
219:
220: /**
221: * The data model which contains information about the tabs, such as the
222: * corresponding component, the icon and the tooltip. Currently this is
223: * assigned in the constructor and cannot be modified later, though this
224: * could be supported in the future (with substantial effort).
225: *
226: * @see TabData
227: * @see TabDataModel
228: */
229: private TabDataModel model;
230:
231: /**
232: * The type of this container, which determines what UI delegate is used for
233: * the tab displayer
234: */
235: private final int type;
236:
237: /**
238: * Holds the value of the active property, determining if the displayer
239: * should be painted with the focused or unfocused colors
240: */
241: private boolean active = false;
242:
243: /**
244: * Flag used to block the call to updateUI() from the superclass constructor
245: * - at that time, none of our instance fields are set, so the UI can't yet
246: * set up the tab displayer correctly
247: */
248: private boolean initialized = false;
249:
250: /**
251: * Utility field holding list of ActionListeners.
252: */
253: private transient List<ActionListener> actionListenerList;
254:
255: /**
256: * Content policy in which all components contained in the data model should immediately
257: * be added to the AWT hierarchy at the time they appear in the data model.
258: *
259: * @see #setContentPolicy
260: */
261: public static final int CONTENT_POLICY_ADD_ALL = 1;
262: /**
263: * Content policy by which components contained in the data model are added to the AWT
264: * hierarchy the first time they are shown, and remain their thereafter unless removed
265: * from the data model.
266: *
267: * @see #setContentPolicy
268: */
269: public static final int CONTENT_POLICY_ADD_ON_FIRST_USE = 2;
270: /**
271: * Content policy by which components contained in the data model are added to the AWT
272: * hierarchy the when they are shown, and removed immediately when the user changes tabs.
273: */
274: public static final int CONTENT_POLICY_ADD_ONLY_SELECTED = 3;
275:
276: private int contentPolicy = DEFAULT_CONTENT_POLICY;
277:
278: /** The default content policy, currently CONTENT_POLICY_ADD_ALL. To facilitate experimentation with
279: * different settings application-wide, set the system property "nb.tabcontrol.contentpolicy"
280: * to 1, 2 or 3 for ADD_ALL, ADD_ON_FIRST_USE or ADD_ONLY_SELECTED, respectively (note other values
281: * will throw an <code>Error</code>). Do not manipulate this value at runtime, it will likely become
282: * a final field in a future release. It is a protected field only to ensure its inclusion in documentation.
283: *
284: * @see #setContentPolicy
285: */
286: protected static int DEFAULT_CONTENT_POLICY = CONTENT_POLICY_ADD_ALL;
287:
288: /** The component converter which will tranlate TabData's from the model into
289: * components. */
290: private ComponentConverter converter = null;
291:
292: /** Winsys info needed for tab control or null if not available */
293: private WinsysInfoForTabbed winsysInfo = null;
294:
295: @Deprecated
296: private LocationInformer locationInformer = null;
297:
298: /**
299: * Create a new pane with the default model and tabs displayer
300: */
301: public TabbedContainer() {
302: this (null, TYPE_VIEW);
303: }
304:
305: /**
306: * Create a new pane with asociated model and the default tabs displayer
307: */
308: public TabbedContainer(TabDataModel model) {
309: this (model, TYPE_VIEW);
310: }
311:
312: public TabbedContainer(int type) {
313: this (null, type);
314: }
315:
316: /**
317: * Create a new pane with the specified model and displayer type
318: *
319: * @param model The model
320: */
321: public TabbedContainer(TabDataModel model, int type) {
322: this (model, type, (WinsysInfoForTabbed) null);
323: }
324:
325: /**
326: * Deprecated, please use constructor with WinsysInfoForTabbed instead.
327: */
328: @Deprecated
329: public TabbedContainer(TabDataModel model, int type,
330: LocationInformer locationInformer) {
331: this (model, type, (WinsysInfoForTabbed) null);
332: this .locationInformer = locationInformer;
333: }
334:
335: /**
336: * Create a new pane with the specified model, displayer type and extra
337: * information from winsys
338: */
339: public TabbedContainer(TabDataModel model, int type,
340: WinsysInfoForTabbed winsysInfo) {
341: switch (type) {
342: case TYPE_VIEW:
343: case TYPE_EDITOR:
344: case TYPE_SLIDING:
345: case TYPE_TOOLBAR:
346: break;
347: default:
348: throw new IllegalArgumentException("Unknown UI type: "
349: + type); //NOI18N
350: }
351: if (model == null) {
352: model = new DefaultTabDataModel();
353: }
354: this .model = model;
355: this .type = Boolean.getBoolean("nb.tabcontrol.alltoolbar") ? TYPE_TOOLBAR
356: : type;
357: this .winsysInfo = winsysInfo;
358: initialized = true;
359: updateUI();
360: //A few borders and such will check this
361: //@see org.netbeans.swing.plaf.gtk.AdaptiveMatteBorder
362: putClientProperty("viewType", new Integer(type)); //NOI18N
363: }
364:
365: /**
366: * Overridden as follows: When called by the superclass constructor (before
367: * the <code>type</code> field is set), it will simply return; the
368: * TabbedContainer constructor will call updateUI() explicitly later.
369: * <p>
370: * Will first search UIManager for a matching UI class. If non-null
371: * (by default it is set in the core/swing/plaf library), it will compare
372: * the found class name with the current UI. If they are a match, it
373: * will call TabbedContainerUI.shouldReplaceUI() to decide whether to
374: * actually do anything or not (in most cases it would just replace an
375: * instance of DefaultTabbedContainerUI with another one; but this call
376: * allows DefaultTabbedContainerUI.uichange() to update the tab displayer
377: * as needed).
378: * <p>
379: * If no UIManager UI class is defined, this method will silently use an
380: * instance of DefaultTabbedContainerUI.
381: */
382: @Override
383: public void updateUI() {
384: if (!initialized) {
385: //Block the superclass call to updateUI(), which comes before the
386: //field is set to tell if we are a view tab control or an editor
387: //tab control - the UI won't be able to set up the tab displayer
388: //correctly until this is set.
389: return;
390: }
391: TabbedContainerUI ui = null;
392: String UIClass = (String) UIManager.get(getUIClassID());
393: if (getUI() != null
394: && (getUI().getClass().getName().equals(UIClass) | UIClass == null)) {
395: if (!getUI().shouldReplaceUI()) {
396: return;
397: }
398: }
399:
400: if (UIClass != null) { //Avoid a stack trace
401: try {
402: ui = (TabbedContainerUI) UIManager.getUI(this );
403: } catch (Error e) {
404: //do nothing
405: }
406: }
407: if (ui != null) {
408: setUI(ui);
409: } else {
410: setUI(DefaultTabbedContainerUI.createUI(this ));
411: }
412: }
413:
414: /**
415: * Get the type of this displayer - it is either TYPE_EDITOR or TYPE_VIEW.
416: * This property is set in the constructor and is immutable
417: */
418: public final int getType() {
419: return type;
420: }
421:
422: /**
423: * Returns <code>TabbedContainer.TABBED_CONTAINER_UI_CLASS_ID</code>
424: */
425: @Override
426: public String getUIClassID() {
427: return TABBED_CONTAINER_UI_CLASS_ID;
428: }
429:
430: /** Get the ui delegate for this component */
431: public TabbedContainerUI getUI() {
432: return (TabbedContainerUI) ui;
433: }
434:
435: /**
436: * Set the converter that converts user objects in the data model into
437: * components to display. If set to null (the default), the user object
438: * at the selected index in the data model will be cast as an instance
439: * of JComponent when searching for what to show for a given tab.
440: * <p>
441: * For use cases where a single component is to be displayed for more
442: * than one tab, just reconfigured when the selection changes, simply
443: * supply a ComponentConverter.Fixed with the component that should be
444: * used for all tabs.
445: */
446: public final void setComponentConverter(ComponentConverter cc) {
447: ComponentConverter old = converter;
448: converter = cc;
449: if (old instanceof ComponentConverter.Fixed
450: && cc instanceof ComponentConverter.Fixed) {
451: List<TabData> l = getModel().getTabs();
452: if (!l.isEmpty()) {
453: TabData[] td = l.toArray(new TabData[0]);
454: getModel().setTabs(new TabData[0]);
455: getModel().setTabs(td);
456: }
457: }
458: }
459:
460: /** Get the component converter which is used to fetch a component
461: * corresponding to an element in the data model. If the value has
462: * not been set, it will use ComponentConverter.DEFAULT, which simply
463: * delegates to TabData.getComponent().
464: */
465: public final ComponentConverter getComponentConverter() {
466: if (converter != null) {
467: return converter;
468: }
469: return ComponentConverter.DEFAULT;
470: }
471:
472: /** Experimental property - alter the policy by which the components in
473: * the model are added to the container. This may not remain suppported.
474: * If used, it should be called before populating the data model.
475: */
476: public final void setContentPolicy(int i) {
477: switch (i) {
478: case CONTENT_POLICY_ADD_ALL:
479: case CONTENT_POLICY_ADD_ON_FIRST_USE:
480: case CONTENT_POLICY_ADD_ONLY_SELECTED:
481: break;
482: default:
483: throw new IllegalArgumentException(
484: "Unknown content policy: " + i);
485: }
486:
487: if (i != contentPolicy) {
488: int old = contentPolicy;
489: contentPolicy = i;
490: firePropertyChange("contentPolicy", old, i); //NOI18N
491: }
492: }
493:
494: /** Determine the policy by which components are added to the container.
495: * There are various pros and cons to each:
496: * <ul>
497: * <li>CONTENT_POLICY_ADD_ALL - All components in the data model are
498: * automatically added to the container, and whenever the model changes,
499: * components are added and removed as need be. This is less scalable,
500: * but absolutely reliable</li>
501: * <li>CONTENT_POLICY_ADD_ON_FIRST_USE - Components are not added to the
502: * container until the first time they are used, and then they remain in
503: * the AWT hierarchy until their TabData elements are removed from the
504: * model. This is more scalable, and probably has some startup time
505: * benefits</li>
506: * <li>CONTENT_POLICY_ADD_ONLY_SELECTED - The only component that will
507: * ever be in the AWT hierarchy is the one that is being displayed. This
508: * is safest in the case that heavyweight AWT components may be used </li>
509: * </ul>
510: */
511: public int getContentPolicy() {
512: return contentPolicy;
513: }
514:
515: @Override
516: public boolean isValidateRoot() {
517: return true;
518: }
519:
520: public boolean isPaintingOrigin() {
521: return true;
522: }
523:
524: public void setToolTipTextAt(int index, String toolTip) {
525: //Do this quietly - no notification is needed
526: TabData tabData = getModel().getTab(index);
527: if (tabData != null) {
528: tabData.tip = toolTip;
529: }
530: }
531:
532: /**
533: * Get the data model that represents the tabs this component has. All
534: * programmatic manipulation of tabs should be done via the data model.
535: *
536: * @return The model
537: */
538: public final TabDataModel getModel() {
539: return model;
540: }
541:
542: /**
543: * Get the selection model. The selection model tracks the index of the
544: * selected component, modifying this index appropriately when tabs are
545: * added or removed.
546: *
547: * @return The model
548: */
549: public final SingleSelectionModel getSelectionModel() {
550: return getUI().getSelectionModel();
551: }
552:
553: /**
554: * Fetch the rectangle of the tab for a given index, in the coordinate space
555: * of this component, by reconfiguring the passed rectangle object
556: */
557: public final Rectangle getTabRect(int index, final Rectangle r) {
558: return getUI().getTabRect(index, r);
559: }
560:
561: /** Gets the index of the tab at point p, or -1 if no tab is there */
562: public int tabForCoordinate(Point p) {
563: return getUI().tabForCoordinate(p);
564: }
565:
566: /**
567: * Set the "active" state of this tab control - this affects the
568: * way the tabs are displayed, to indicate focus. Note that this method
569: * will <i>never</i> be called automatically in stand-alone use of
570: * TabbedContainer. While one would expect a component gaining keyboard
571: * focus to be a good determinant, it actually turns out to be a potent
572: * source of subtle and hard-to-fix bugs.
573: * <p/>
574: * NetBeans uses an AWTEventListener to track mouse clicks, and allows
575: * components to become activated only via a mouse click or via a keyboard
576: * action or menu item which activates the component. This approach is far
577: * more robust and is the recommended usage pattern.
578: */
579: public final void setActive(boolean active) {
580: if (active != this .active) {
581: this .active = active;
582: firePropertyChange(PROP_ACTIVE, !active, active);
583: }
584: }
585:
586: /**
587: * Cause the tab at the specified index to blink or otherwise suggest that
588: * the user should click it.
589: */
590: public final void requestAttention(int tab) {
591: getUI().requestAttention(tab);
592: }
593:
594: public final void cancelRequestAttention(int tab) {
595: getUI().cancelRequestAttention(tab);
596: }
597:
598: /**
599: * Cause the specified tab to blink or otherwisse suggest that the user should
600: * click it.
601: */
602: public final boolean requestAttention(TabData data) {
603: int idx = getModel().indexOf(data);
604: boolean result = idx >= 0;
605: if (result) {
606: requestAttention(idx);
607: }
608: return result;
609: }
610:
611: public final void cancelRequestAttention(TabData data) {
612: int idx = getModel().indexOf(data);
613: if (idx != -1) {
614: cancelRequestAttention(idx);
615: }
616: }
617:
618: /**
619: * Determine if this component thinks it is "active", which
620: * affects how the tabs are painted - typically used to indicate that
621: * keyboard focus is somewhere within the component
622: */
623: public final boolean isActive() {
624: return active;
625: }
626:
627: /**
628: * Register an ActionListener. TabbedContainer and TabDisplayer guarantee
629: * that the type of event fired will always be TabActionEvent. There are
630: * two special things about TabActionEvent: <ol> <li>There are methods on
631: * TabActionEvent to find the index of the tab the event was performed on,
632: * and if present, retrieve the mouse event that triggered it, for clients
633: * that wish to provide different handling for different mouse buttons</li>
634: * <li>TabActionEvents can be consumed. If a listener consumes the event,
635: * the UI will take no action - the selection will not be changed, the tab
636: * will not be closed. Consuming the event means taking responsibility for
637: * doing whatever would normally happen automatically. This is useful for,
638: * for example, showing a dialog and possibly aborting closing a tab if it
639: * contains unsaved data, for instance.</li> </ol> Action events will be
640: * fired <strong>before</strong> any action has been taken to alter the
641: * state of the control to match the action, so that they may be vetoed or
642: * modified by consuming the event.
643: *
644: * @param listener The listener to register.
645: */
646: public final synchronized void addActionListener(
647: ActionListener listener) {
648: if (actionListenerList == null) {
649: actionListenerList = new ArrayList<ActionListener>();
650: }
651: actionListenerList.add(listener);
652: }
653:
654: /**
655: * Remove an action listener.
656: *
657: * @param listener The listener to remove.
658: */
659: public final synchronized void removeActionListener(
660: ActionListener listener) {
661: if (actionListenerList != null) {
662: actionListenerList.remove(listener);
663: if (actionListenerList.isEmpty()) {
664: actionListenerList = null;
665: }
666: }
667: }
668:
669: /**
670: * Used by the UI to post action events for selection and close operations.
671: * If the event is consumed, the UI should take no action to change the
672: * selection or close the tab, and will presume that the receiver of the
673: * event is handling performing whatever action is appropriate.
674: *
675: * @param event The event to be fired
676: */
677: protected final void postActionEvent(TabActionEvent event) {
678: List<ActionListener> list;
679: synchronized (this ) {
680: if (actionListenerList == null)
681: return;
682: list = Collections.unmodifiableList(actionListenerList);
683: }
684: for (ActionListener l : list) {
685: l.actionPerformed(event);
686: }
687: }
688:
689: public void setIconAt(int index, Icon icon) {
690: getModel().setIcon(index, icon);
691: }
692:
693: public void setTitleAt(int index, String title) {
694: getModel().setText(index, title);
695: }
696:
697: /** Create an image of a single tab, suitable for use in drag and drop operations */
698: public Image createImageOfTab(int idx) {
699: return getUI().createImageOfTab(idx);
700: }
701:
702: /** Get the number of tabs. Equivalent to <code>getModel().size()</code> */
703: public int getTabCount() {
704: return getModel().size();
705: }
706:
707: /**
708: * Set whether or not close buttons should be shown.
709: * This can be defaulted with the system property
710: * <code>nb.tabs.suppressCloseButton</code>; if the system
711: * property is not set, the default is true.
712: */
713: public final void setShowCloseButton(boolean val) {
714: boolean wasShow = isShowCloseButton();
715: if (val != wasShow) {
716: getUI().setShowCloseButton(val);
717: firePropertyChange("showCloseButton", wasShow, val);
718: }
719: }
720:
721: /**
722: * Determine whether or not close buttons are being shown.
723: */
724: public final boolean isShowCloseButton() {
725: return getUI().isShowCloseButton();
726: }
727:
728: /** Get the index of a component */
729: public int indexOf(Component comp) {
730: int max = getModel().size();
731: TabDataModel mdl = getModel();
732: for (int i = 0; i < max; i++) {
733: if (getComponentConverter().getComponent(mdl.getTab(i)) == comp) {
734: return i;
735: }
736: }
737: return -1;
738: }
739:
740: /** The index at which a tab should be inserted if a drop operation
741: * occurs at this point.
742: *
743: * @param location A point anywhere on the TabbedContainer
744: * @return A tab index, or -1
745: */
746: public int dropIndexOfPoint(Point location) {
747: return getUI().dropIndexOfPoint(location);
748: }
749:
750: /**
751: * Get a shape appropriate for drawing on the window's glass pane to indicate
752: * where a component should appear in the tab order if it is dropped here.
753: *
754: * @param dragged An object being dragged, or null. The object may be an instance
755: * of <code>TabData</code> or <code>Component</code>, in which case a check
756: * will be done of whether the dragged object is already in the data model,
757: * so that attempts to drop the object over the place it already is in the
758: * model will always return the exact indication of that tab's position.
759: *
760: * @param location A point
761: * @return Drop indication drawing
762: */
763: public Shape getDropIndication(Object dragged, Point location) {
764: int ix;
765: if (dragged instanceof Component) {
766: ix = indexOf((Component) dragged);
767: } else if (dragged instanceof TabData) {
768: ix = getModel().indexOf((TabData) dragged);
769: } else {
770: ix = -1;
771: }
772:
773: int over = dropIndexOfPoint(location);
774:
775: if (over < 0) { // XXX PENDING The tab is not found, later simulate the last one.
776: Rectangle r = getBounds();
777: r.setLocation(0, 0);
778: return r;
779: }
780: /*
781: if (over == ix || (over == ix + 1 && ix != -1 && over < getModel().size())) { //+1 - dropping on the next tab will put it in the same place
782: return getUI().getExactTabIndication(over);
783: } else {
784: return getUI().getInsertTabIndication(over);
785: }
786: */
787: if (over == ix && ix != -1) {
788: return getUI().getExactTabIndication(over);
789: } else {
790: return getUI().getInsertTabIndication(over);
791: }
792: }
793:
794: @Deprecated
795: public LocationInformer getLocationInformer() {
796: return locationInformer;
797: }
798:
799: public WinsysInfoForTabbed getWinsysInfo() {
800: return winsysInfo;
801: }
802:
803: static {
804: //Support for experimenting with different content policies in NetBeans
805: String s = System.getProperty("nb.tabcontrol.contentpolicy"); //NOI18N
806: if (s != null) {
807: try {
808: DEFAULT_CONTENT_POLICY = Integer.parseInt(s);
809: switch (DEFAULT_CONTENT_POLICY) {
810: case CONTENT_POLICY_ADD_ALL:
811: case CONTENT_POLICY_ADD_ON_FIRST_USE:
812: case CONTENT_POLICY_ADD_ONLY_SELECTED:
813: System.err.println("Using custom content policy: "
814: + DEFAULT_CONTENT_POLICY);
815: break;
816: default:
817: throw new Error("Bad value for default content "
818: + "policy: " + s + " only values 1, 2 or 3"
819: + "are meaningful"); //NOI18N
820: }
821: System.err.println("Default content policy is "
822: + DEFAULT_CONTENT_POLICY);
823: } catch (Exception e) {
824: System.err.println("Error parsing default content "
825: + "policy: \"" + s + "\""); //NOI18N
826: }
827: }
828: }
829:
830: @Override
831: public javax.accessibility.AccessibleContext getAccessibleContext() {
832: if (null == accessibleContext) {
833: accessibleContext = new AccessibleJComponent() {
834: @Override
835: public AccessibleRole getAccessibleRole() {
836: return AccessibleRole.PAGE_TAB_LIST;
837: }
838: };
839:
840: accessibleContext.setAccessibleName(NbBundle.getMessage(
841: TabbedContainer.class, "ACS_TabbedContainer"));
842: accessibleContext.setAccessibleDescription(NbBundle
843: .getMessage(TabbedContainer.class,
844: "ACSD_TabbedContainer"));
845: }
846:
847: return accessibleContext;
848: }
849:
850: //+++++++++++++++++++++++++
851: //Begin: Transparency support
852: //+++++++++++++++++++++++++
853: private static final float ALPHA_TRESHOLD = 0.2f;
854: private float currentAlpha = 1.0f;
855:
856: /**
857: * @return True if the container is transparent, i.e. painted if Alpha set to 0.2 or less.
858: */
859: public boolean isTransparent() {
860: return isSliding() && currentAlpha <= ALPHA_TRESHOLD;
861: }
862:
863: /**
864: * Turn container transparency on/off
865: * @param transparent True to make the container transparent
866: */
867: public void setTransparent(boolean transparent) {
868: if (!isSliding()) {
869: //support only slided-in windows
870: throw new IllegalStateException(
871: "Transparency is supported for sliding windows only.");
872: }
873: float oldAlpha = currentAlpha;
874: currentAlpha = transparent ? ALPHA_TRESHOLD : 1.0f;
875: if (oldAlpha != currentAlpha) {
876: repaint();
877: }
878: }
879:
880: AWTEventListener awtListener = null;
881:
882: private AWTEventListener getAWTListener() {
883: if (null == awtListener) {
884: awtListener = new AWTEventListener() {
885: public void eventDispatched(AWTEvent event) {
886: if (event.getID() == MouseEvent.MOUSE_PRESSED) {
887: //ignore mouse clicks outside this container
888: if (event.getSource() instanceof Component
889: && !SwingUtilities.isDescendingFrom(
890: (Component) event.getSource(),
891: TabbedContainer.this ))
892: return;
893: if (((MouseEvent) event).getButton() != MouseEvent.BUTTON3)
894: setTransparent(false);
895: } else if (event.getID() == KeyEvent.KEY_PRESSED) {
896: KeyEvent ke = (KeyEvent) event;
897: //TODO make shortcut configurable
898: if (ke.getKeyCode() == KeyEvent.VK_NUMPAD0
899: && ke.isControlDown()) {
900: setTransparent(!isTransparent());
901: ke.consume();
902: return;
903: }
904: if (!(ke.getKeyCode() == KeyEvent.VK_ALT
905: || ke.getKeyCode() == KeyEvent.VK_SHIFT
906: || ke.getKeyCode() == KeyEvent.VK_META || ke
907: .getKeyCode() == KeyEvent.VK_CONTROL)) {
908: setTransparent(false);
909: }
910: } else if (event.getID() == MouseEvent.MOUSE_PRESSED) {
911: setTransparent(false);
912: } else if (event.getID() == KeyEvent.KEY_PRESSED) {
913: KeyEvent ke = (KeyEvent) event;
914: if (!(ke.getKeyCode() == KeyEvent.VK_ALT || ke
915: .getKeyCode() == KeyEvent.VK_SHIFT))
916: setTransparent(false);
917: }
918: }
919: };
920: }
921: return awtListener;
922: }
923:
924: /**
925: * @return True if the container holds slided-in window.
926: */
927: private boolean isSliding() {
928: boolean res = false;
929: if (getModel().size() == 1) {
930: Component c = getModel().getTab(0).getComponent();
931: if (c instanceof JComponent) {
932: Object val = ((JComponent) c)
933: .getClientProperty("isSliding"); //NOI18N
934: res = null != val && val instanceof Boolean
935: && ((Boolean) val).booleanValue();
936: }
937: }
938: return res;
939: }
940:
941: @Override
942: public void addNotify() {
943: super .addNotify();
944: if (isSliding()) {
945: //register AWT listener in case the inner top component has its only mouse listener
946: Toolkit.getDefaultToolkit().addAWTEventListener(
947: getAWTListener(),
948: MouseEvent.MOUSE_WHEEL_EVENT_MASK
949: + MouseEvent.MOUSE_EVENT_MASK
950: + KeyEvent.KEY_EVENT_MASK);
951: }
952: }
953:
954: @Override
955: public void removeNotify() {
956: if (null != awtListener) {
957: Toolkit.getDefaultToolkit().removeAWTEventListener(
958: awtListener);
959: awtListener = null;
960: }
961: super .removeNotify();
962: currentAlpha = 1.0f;
963: }
964:
965: @Override
966: public void paint(Graphics g) {
967: if (isSliding() && currentAlpha != 1.0f) {
968: Graphics2D g2d = (Graphics2D) g;
969: Composite oldComposite = g2d.getComposite();
970: g2d.setComposite(AlphaComposite.getInstance(
971: AlphaComposite.SRC_OVER, currentAlpha));
972: super .paint(g);
973: g2d.setComposite(oldComposite);
974: } else {
975: super .paint(g);
976: }
977: }
978: //+++++++++++++++++++++++++
979: //End: Transparency support
980: //+++++++++++++++++++++++++
981: }
|