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: * AbstractTabDisplayerUI.java
043: *
044: * Created on March 16, 2004, 6:16 PM
045: */
046:
047: package org.netbeans.swing.tabcontrol.plaf;
048:
049: import org.netbeans.swing.tabcontrol.TabDisplayer;
050: import org.netbeans.swing.tabcontrol.TabDisplayerUI;
051: import org.netbeans.swing.tabcontrol.event.ComplexListDataEvent;
052: import org.netbeans.swing.tabcontrol.event.ComplexListDataListener;
053:
054: import javax.swing.*;
055: import javax.swing.event.ChangeEvent;
056: import javax.swing.event.ChangeListener;
057: import javax.swing.event.ListDataEvent;
058: import java.awt.*;
059: import java.awt.event.*;
060: import java.beans.PropertyChangeEvent;
061: import java.beans.PropertyChangeListener;
062:
063: /**
064: * Base class for the implementations of TabDisplayerUI in this package. Uses
065: * TabLayoutModel for managing the layout of the tabs. Defines an SPI for
066: * UI delegates for TabDisplayer.
067: * <p>
068: * For most use cases, it will make more sense to subclass BasicTabDisplayerUI or
069: * BasicScrollingTabDisplayerUI, which handle most of the logic any implementation
070: * will need.
071: *
072: *
073: * @see BasicTabDisplayerUI
074: * @see BasicScrollingTabDisplayerUI
075: *
076: * @author Tim Boudreau
077: */
078: public abstract class AbstractTabDisplayerUI extends TabDisplayerUI {
079: /**
080: * Layout model, which will be initialized in <code>installUI()</code> by calling
081: * <code>createLayoutModel</code>. The layout model provides tab coordinates.
082: */
083: protected TabLayoutModel layoutModel = null;
084: /**
085: * Mouse listener (which may optionally implement MouseWheelListener and MouseMotionListener),
086: * which handles mouse events over the tab, triggering selection model changes, repaints, etc.
087: */
088: protected MouseListener mouseListener = null;
089: /**
090: * Component listener - mainly used to detach listeners when the component is hidden,
091: * and reattach them when it is shown. Also used by BasicScrollingTabDisplayerUI to
092: * trigger re-layouts of scrolled tabs when the size of the component changes.
093: */
094: protected ComponentListener componentListener = null;
095: /**
096: * A property change listener to listen on any changes from the component which should
097: * trigger repainting or other operations. The default implementation simply listens for
098: * changes in the <code>active</code> property to trigger a repaint.
099: */
100: protected PropertyChangeListener propertyChangeListener = null;
101: /**
102: * Listener on the TabDataModel. Responsible for repainting on model changes. Note that
103: * DefaultTabSelectionModel also listens on the model and automatically updates the
104: * selected index if, say, tabs are inserted before the currently selected tab.
105: */
106: protected ModelListener modelListener = null;
107: /**
108: * A change listener which listens on the selection model and repaints as needed when
109: * the selection changes.
110: */
111: protected ChangeListener selectionListener = null;
112:
113: protected HierarchyListener hierarchyListener = null;
114:
115: /**
116: * Creates a new instance of AbstractTabDisplayerUI
117: */
118: public AbstractTabDisplayerUI(TabDisplayer displayer) {
119: super (displayer);
120: }
121:
122: /** installUI is final to ensure listeners, etc. are created and attached. Subclasses that
123: * need to perform initialization on install should override <code>install()</code>,
124: * and refer to the <code>displayer</code> instance field, which will be initialized here.
125: *
126: * @param c An instance of TabDisplayer
127: */
128: public final void installUI(JComponent c) {
129: assert c == displayer;
130: super .installUI(c); //installs the selection model
131: ToolTipManager.sharedInstance().registerComponent(displayer);
132: layoutModel = createLayoutModel();
133: mouseListener = createMouseListener();
134: componentListener = createComponentListener();
135: modelListener = createModelListener();
136: propertyChangeListener = createPropertyChangeListener();
137: selectionListener = createSelectionListener();
138: hierarchyListener = createHierarchyListener();
139: install();
140: installListeners();
141: displayer.setFont(createFont());
142: }
143:
144: /** This method is final - subclasses that need to deinitialize should override
145: * <code>uninstall()</code>, and remove any listeners, null any unneeded references, etc.
146: *
147: * @param c
148: */
149: public final void uninstallUI(JComponent c) {
150: assert c == displayer;
151: ToolTipManager.sharedInstance().unregisterComponent(displayer);
152: super .uninstallUI(c);
153: // #47644 - first uninstallListeners, then uninstall, so that tabState can't be null while
154: // listeners are active. Only probable fix, but certainly right thing to do.
155: uninstallListeners();
156: uninstall();
157: layoutModel = null;
158: mouseListener = null;
159: selectionModel = null;
160: componentListener = null;
161: selectionListener = null;
162: }
163:
164: /**
165: * Called after creating the layout model, selection model and mouse
166: * listener, but before installing the mouse listener and selection model.
167: * Subclasses may use this method to do anything they need to do at ui
168: * install time.
169: */
170: protected void install() {
171: //do nothing
172: }
173:
174: /**
175: * Called after uninstalling the mouse listener and selection model, but
176: * before references to that or the layout model or displayer have been
177: * nulled. Subclasses may use this method to do any cleanup they need to do
178: * at uninstall time.
179: */
180: protected void uninstall() {
181: //do nothing
182: }
183:
184: /**
185: * Installs the mouse listener returned by createMouseListener into the
186: * control. If the mouse listener implements MouseMotionListener or
187: * MouseWheelListener, it will be installed as such as well.
188: */
189: protected final void installListeners() {
190: displayer.addHierarchyListener(hierarchyListener);
191: displayer.addPropertyChangeListener(propertyChangeListener);
192: if (componentListener != null) {
193: displayer.addComponentListener(componentListener);
194: }
195: displayer.getModel().addComplexListDataListener(modelListener);
196: displayer.getModel().addChangeListener(modelListener);
197: if (mouseListener != null) {
198: displayer.addMouseListener(mouseListener);
199: if (mouseListener instanceof MouseMotionListener) {
200: displayer
201: .addMouseMotionListener((MouseMotionListener) mouseListener);
202: }
203: if (mouseListener instanceof MouseWheelListener) {
204: displayer
205: .addMouseWheelListener((MouseWheelListener) mouseListener);
206: }
207: }
208: selectionModel.addChangeListener(selectionListener);
209: }
210:
211: /**
212: * Installs the mouse listener returned by createMouseListener into the
213: * control. If the mouse listener implements MouseMotionListener or
214: * MouseWheelListener, it will be removed as such as well.
215: */
216: protected final void uninstallListeners() {
217: if (mouseListener instanceof MouseMotionListener) {
218: displayer
219: .removeMouseMotionListener((MouseMotionListener) mouseListener);
220: }
221: if (mouseListener instanceof MouseWheelListener) {
222: displayer
223: .removeMouseWheelListener((MouseWheelListener) mouseListener);
224: }
225: if (mouseListener != null) {
226: displayer.removeMouseListener(mouseListener);
227: }
228: if (componentListener != null) {
229: displayer.removeComponentListener(componentListener);
230: }
231: displayer.getModel().removeComplexListDataListener(
232: modelListener);
233: displayer.getModel().removeChangeListener(modelListener);
234: displayer.removePropertyChangeListener(propertyChangeListener);
235: displayer.removeHierarchyListener(hierarchyListener);
236: selectionModel.removeChangeListener(selectionListener);
237: mouseListener = null;
238: componentListener = null;
239: propertyChangeListener = null;
240: selectionListener = null;
241: modelListener = null;
242: hierarchyListener = null;
243: }
244:
245: protected HierarchyListener createHierarchyListener() {
246: return new DisplayerHierarchyListener();
247: }
248:
249: /**
250: * Create an instance of TabLayoutModel which will provide coordinates for
251: * tabs
252: */
253: protected abstract TabLayoutModel createLayoutModel(); //XXX move this to BasicTabDisplayerUI
254:
255: /**
256: * Create the mouse listener that will be responsible for changing the
257: * selection on mouse events, triggering repaints on mouse enter/exit/motion, etc.
258: * The installation code will detect if the resulting listener also implements
259: * MouseWheelListener or MouseMotionListener, and if so, will add it as such.
260: *
261: * @return A mouse listener, which may also implement MouseMotionListener and/or
262: * MouseWheelListener
263: */
264: protected abstract MouseListener createMouseListener();
265:
266: /**
267: * Create a <code>ChangeListener</code> to be attached to the selection model. This
268: * listener will be responsible for repainting the appropriate areas on selection changes.
269: *
270: * @return A changeListener that will be notified of selection changes
271: */
272: protected abstract ChangeListener createSelectionListener();
273:
274: protected Font createFont() {
275: return UIManager.getFont("controlFont"); //NOI18N
276: }
277:
278: /**
279: * Create a listener on the data model that triggers repaints on appropriate
280: * changes.
281: */
282: protected ModelListener createModelListener() {
283: return new ModelListener();
284: }
285:
286: /**
287: * Create a ComponentListener that may be needed to handle resize, show,
288: * hide, etc. Returns null by default.
289: */
290: protected ComponentListener createComponentListener() {
291: return null;
292: }
293:
294: /**
295: * Create a PropertyChangeListener which listens on any interesting
296: * properties of the control
297: */
298: protected PropertyChangeListener createPropertyChangeListener() {
299: return new DisplayerPropertyChangeListener();
300: }
301:
302: /** Creates an instance of <code>DefaultTabSelectionModel</code> */
303: protected SingleSelectionModel createSelectionModel() {
304: return new DefaultTabSelectionModel(displayer.getModel());
305: }
306:
307: /**
308: * A very basic implementation of dropIndexOfPoint, which simply iterates
309: * all of the tab rectangles to see if they contain the point. It is
310: * preferred to override this and provide a more efficient implementation
311: * unless the UI is not designed to display more than a few tabs.
312: *
313: * @param p A point
314: * @return The index, or -1 if none
315: */
316: public int dropIndexOfPoint(Point p) {
317: Point p2 = toDropPoint(p);
318: int max = displayer.getModel().size();
319: for (int i = 0; i < max; i++) {
320: Rectangle r = getTabRect(i, null);
321: if (r.contains(p2)) {
322: return i;
323: }
324: }
325: return -1;
326: }
327:
328: /** Convenience method called by ModelListener.stateChanged() when the data model changes.
329: * Eliminates the need for custom subclasses where the only purpose is to discard some
330: * small amount of cached data. The default implementation simply calls <code>displayer.repaint()</code>.
331: */
332: protected void modelChanged() {
333: displayer.repaint();
334: }
335:
336: private Point scratchPoint = new Point();
337:
338: /** Converts a point into a point in the coordinate space of the tabs area, for
339: * determining what index a drop operation should affect.
340: *
341: * @param location A point in the coordinate space of the container
342: * @return A point in the coordinate space of the tab display area
343: */
344: protected Point toDropPoint(Point location) {
345: //Construct a point within the tabs area retaining the relevant coordinate that
346: //will allow it to work
347: if (displayer.getWidth() > displayer.getHeight()) {
348: //horizontal tabs area
349: scratchPoint.setLocation(location.x,
350: (displayer.getHeight() / 2));
351: } else {
352: //vertical tabs area
353: scratchPoint.setLocation(displayer.getWidth() / 2,
354: location.y);
355: }
356: return scratchPoint;
357: }
358:
359: /** Does nothing, no shortcuts */
360: public void unregisterShortcuts(JComponent comp) {
361: // no operation
362: }
363:
364: /** Does nothing, no shortcuts */
365: public void registerShortcuts(JComponent comp) {
366: // no operation
367: }
368:
369: /**
370: * A property change listener which will repaint the selected tab when the
371: * "active" property changes on the tab displayer
372: */
373: protected class DisplayerPropertyChangeListener implements
374: PropertyChangeListener {
375: public void propertyChange(PropertyChangeEvent e) {
376: if (displayer.isShowing()
377: && TabDisplayer.PROP_ACTIVE.equals(e
378: .getPropertyName())) {
379: activationChanged();
380: }
381: }
382:
383: /**
384: * Called if PROP_ACTIVE on the displayer changes
385: */
386: protected void activationChanged() {
387: int i = selectionModel.getSelectedIndex();
388: if (i != -1) {
389: Rectangle r = new Rectangle();
390: getTabRect(i, r);
391: if (r.width != 0 && r.height != 0) {
392: displayer.repaint(r.x, r.y, r.width, r.height);
393: }
394: }
395: }
396: }
397:
398: /**
399: * A hierarchy listener which registers the component with ToolTipManager
400: * when displayed, and de-registers it when hidden
401: */
402: protected class DisplayerHierarchyListener implements
403: HierarchyListener {
404: public DisplayerHierarchyListener() {
405:
406: }
407:
408: public void hierarchyChanged(HierarchyEvent e) {
409: if (e.getChanged() == displayer
410: && (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
411: if (displayer.isShowing()) {
412: ToolTipManager.sharedInstance().registerComponent(
413: displayer);
414: } else {
415: ToolTipManager.sharedInstance()
416: .unregisterComponent(displayer);
417: }
418: }
419: }
420: }
421:
422: /**
423: * Simple implementation of a listener on a TabDataModel. The default implementation
424: * simply does a full repaint of the tabs area on a ChangeEvent. More optimized
425: * implementations are possible by handling ListDataEvents.
426: */
427: protected class ModelListener implements ComplexListDataListener,
428: ChangeListener {
429:
430: private boolean checkVisible = false;
431:
432: /**
433: * No-op implementation
434: */
435: public void contentsChanged(ListDataEvent e) {
436: //do nothing
437: }
438:
439: /**
440: * No-op implementation
441: */
442: public void indicesAdded(ComplexListDataEvent e) {
443: //do nothing
444: }
445:
446: /**
447: * No-op implementation
448: */
449: public void indicesChanged(ComplexListDataEvent e) {
450: //do nothing
451: }
452:
453: /**
454: * No-op implementation
455: */
456: public void indicesRemoved(ComplexListDataEvent e) {
457: //do nothing
458: }
459:
460: /**
461: * No-op implementation
462: */
463: public void intervalAdded(ListDataEvent e) {
464: //do nothing
465: }
466:
467: /**
468: * No-op implementation
469: */
470: public void intervalRemoved(ListDataEvent e) {
471: //do nothing
472: }
473:
474: /**
475: * Called whenever any change happens in the data model (one of the above methods will also
476: * be called with specific data about the change). This method is final, and simply calls
477: * <code>modelChanged()</code>. To discard some cached data when the model changes, simply
478: * override that.
479: */
480: public final void stateChanged(ChangeEvent e) {
481: modelChanged();
482: }
483: }
484:
485: }
|