0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.swing.tabcontrol.plaf;
0042:
0043: import java.awt.event.ActionEvent;
0044: import java.awt.event.ActionListener;
0045: import java.util.ArrayList;
0046: import java.util.Arrays;
0047: import java.util.HashSet;
0048: import java.util.Iterator;
0049: import java.util.List;
0050: import java.util.Set;
0051: import javax.swing.Timer;
0052: import javax.swing.event.ListDataEvent;
0053: import org.netbeans.swing.tabcontrol.TabData;
0054: import org.netbeans.swing.tabcontrol.event.ArrayDiff;
0055: import org.netbeans.swing.tabcontrol.event.ComplexListDataEvent;
0056: import org.netbeans.swing.tabcontrol.event.VeryComplexListDataEvent;
0057: import org.openide.util.Utilities;
0058:
0059: /**
0060: * Used by BasicTabDisplayerUI and its subclasses.
0061: * Tracks and manages the state of tabs, mainly which one currently contains the
0062: * mouse, if the mouse is in the close button, if the tab is adjacent to a
0063: * selected tab, if it is leftmost, rightmost, active, etc. This class hides
0064: * most of the complexity of deciding what mouse events should trigger a repaint
0065: * of what areas in an optimized way. It provides setters which a mouse
0066: * listener can call to indicate that the mouse has, say, moved into a tab, or
0067: * from one tab to another, or the selection has changed, etc.
0068: * <p>
0069: * Essentially, this class is fed indices of tabs that have various states (selected,
0070: * contains mouse, etc.), figures out if this affects one tab, two tabs (a different
0071: * tab had the state, such as the mouse moving from one tab to another) or all tabs (activated). It determines
0072: * a change type, and consults a <i>repaint policy</i> (an integer bitmask) to decide
0073: * if one, both, all or no tabs should be repainted.
0074: * <p>
0075: * The typical use case is to implement a subclass, and override getState() to
0076: * mix in bitmasks for things like whether a tab is clipped, etc. - things the
0077: * base implementation can't know about.
0078: *
0079: * <p>
0080: * Subclasses implement the <code>repaintTab()</code> method to do the actual
0081: * repainting, and implement <code>getRepaintPolicy()</code>. The repainting will be
0082: * called if an event happens that changes the state in a way that the repaint policy
0083: * bitmask indicates should cause a repaint.
0084: * <p>
0085: * BasicTabDisplayerUI implements a mouse listener which will call the appropriate
0086: * methods when the mouse enters/exits tabs, etc.
0087: * <h4>Details</h4>
0088: * <p>
0089: * State is composed as an integer bitmask which covers all of the supported
0090: * states of a tab that may affect the way they paint. These are also the values that
0091: * are passed to the methods of a <code>TabCellRenderer</code> to tell it how to
0092: * paint itself. Two other integer
0093: * bitmasks are used: The <code>changeType</code>, which indicates whether a
0094: * change was from one tab to another tab, one tab to no tab (i.e. selection set
0095: * to -1), one tab to the same tab (i.e. the mouse moved out of the tab control,
0096: * and so the tab with the mouse in it is now no tab). RepaintPolicy is an integer
0097: * bitmask composed of conditions under which the control should repaint one or all
0098: * tabs, and determines what types of changes actually trigger repaints.
0099: * <p>
0100: * Subclasses are expected to override <code>getState()</code> to provide
0101: * information about non-mouse-or-focus related states, such as clipping of
0102: * scrollable tabs. Predefined states for tabs are: CLIP_RIGHT, CLIP_LEFT,
0103: * ARMED, PRESSED, SELECTED, ACTIVE, NOT_ONSCREEN, LEFTMOST, RIGHTMOST,
0104: * CLOSE_BUTTON_ARMED, BEFORE_SELECTED, AFTER_SELECTED, MOUSE_IN_TABS_AREA,
0105: * MOUSE_PRESSED_IN_CLOSE_BUTTON. Subclasses must handle returning the following
0106: * states if they wish to, which are not handled directly in TabState: LEFTMOST,
0107: * RIGHTMOST, CLIP_LEFT, CLIP_RIGHT, NOT_ONSCREEN.
0108: * <p>
0109: * Most of the states are fairly self-explanatory; NOT_ONSCREEN is useful as an
0110: * optimization so that no work is done for events that would try to produce a
0111: * repaint for something not visible; CLIP_* refers to the case in scrollable
0112: * tab UIs, in which a tab may only be partially visible;
0113: * MOUSE_PRESSED_IN_CLOSE_BUTTON is distinct because the CLOSE_BUTTON_ARMED
0114: * state will be reset if the mouse moves out of the close button area, but UIs
0115: * should perform a close action if the mouse was pressed over the close button,
0116: * moved away from the close button and then back to it, so this state preserves
0117: * the information that the originating location of a mouse press was in the
0118: * close button.
0119: */
0120: public abstract class TabState {
0121:
0122: /**
0123: * Bitmask for state of tabs clipped on the right side - that is, partially
0124: * displayed
0125: */
0126: public static final int CLIP_RIGHT = 1;
0127: /**
0128: * Bitmask for state of tabs clipped on the right side - that is, partially
0129: * displayed
0130: */
0131: public static final int CLIP_LEFT = 2;
0132: /**
0133: * Bitmask indicating the tab contains the mouse
0134: */
0135: public static final int ARMED = 4;
0136: /**
0137: * Bitmask indicating the tab contains the mouse and the mouse button has
0138: * been pressed
0139: */
0140: public static final int PRESSED = 8;
0141: /**
0142: * Bitmask indicating the tab is selected
0143: */
0144: public static final int SELECTED = 16;
0145: /**
0146: * Bitmask indicating the tab is activated
0147: */
0148: public static final int ACTIVE = 32;
0149: /**
0150: * State bitmask indicating a tab is not displayed at all and shouldn't be
0151: * painted. Implementations may more simply avoid not painting a tab by not
0152: * including it in the range returned by getFirstVisibleTab()/getLastVisibleTab().
0153: * This state exists so that implementations have the option of returning
0154: * the entire range of tabs as visible and determining if one is truly
0155: * visble or not in getTabState() - sometimes doing it this way will be less
0156: * expensive.
0157: */
0158: public static final int NOT_ONSCREEN = 64;
0159: /**
0160: * Bitmask indicating the tab is at the extreme left and
0161: * <strong>not</strong> clipped
0162: */
0163: public static final int LEFTMOST = 128;
0164: /**
0165: * Bitmask indicating the tab is at the extreme right and
0166: * <strong>not</strong> clipped
0167: */
0168: public static final int RIGHTMOST = 256;
0169: /**
0170: * Bitmask indicating that the tab contains the mouse and the mouse is in
0171: * the close button
0172: */
0173: public static final int CLOSE_BUTTON_ARMED = 512;
0174: /**
0175: * Bitmask indicating that the tab's index is that of the selected index
0176: * less one
0177: */
0178: public static final int BEFORE_SELECTED = 1024;
0179: /**
0180: * Bitmask indicating that the tab's index is that of the selected index
0181: * plus one
0182: */
0183: public static final int AFTER_SELECTED = 2048;
0184: /**
0185: * Bitmask indicating that the mouse is in the tabs area
0186: */
0187: public static final int MOUSE_IN_TABS_AREA = 4096;
0188:
0189: /**
0190: * Bitmask indicating that the mouse is inside the close button and has
0191: * been pressed.
0192: */
0193: public static final int MOUSE_PRESSED_IN_CLOSE_BUTTON = 8192;
0194:
0195: /**
0196: * Bitmask indicating that the tab is in "attention" mode - blinking or
0197: * flashing to get the user's attention.
0198: */
0199: public static final int ATTENTION = 16384;
0200:
0201: /**
0202: * Bitmask indicating that the tab's index is that of the armed index
0203: * less one
0204: */
0205: public static final int BEFORE_ARMED = 32768;
0206:
0207: /**
0208: * Indicates the last constant defined - renderers that wish to add their
0209: * own bitmasks should use multiples of this number
0210: */
0211: public static int STATE_LAST = MOUSE_PRESSED_IN_CLOSE_BUTTON;
0212:
0213: private int pressedIndex = -1;
0214: private int containsMouseIndex = -1;
0215: private int closeButtonContainsMouseIndex = -1;
0216: private int mousePressedInCloseButtonIndex = -1;
0217: private boolean mouseInTabsArea = false;
0218: private boolean active = false;
0219: private int selectedIndex = -1;
0220:
0221: private int prev = -1;
0222: private int curr = -1;
0223: private int lastChangeType = NO_CHANGE;
0224: private int lastAffected = 0;
0225: private int lastChange = 0;
0226:
0227: /** Repaint policy bitmask indicating that a tab should be repainted whenever the mouse enters or exits it */
0228: public static final int REPAINT_ON_MOUSE_ENTER_TAB = 1;
0229: /** Repaint policy bitmask indicating that all tabs should be repainted whenever the mouse enters or leaves the
0230: * area in which tabs are painted */
0231: public static final int REPAINT_ALL_ON_MOUSE_ENTER_TABS_AREA = 3;
0232: /** Repaint policy bitmask indicating that the tab should be repainted when the mouse enters or exits the close
0233: * button region */
0234: public static final int REPAINT_ON_MOUSE_ENTER_CLOSE_BUTTON = 4;
0235: /** Repaint policy bitmask indicating that the tab should be repainted on mouse pressed events */
0236: public static final int REPAINT_ON_MOUSE_PRESSED = 8;
0237: /** Repaint policy bitmask indicating that the selected tab should be repainted when the activated state changes */
0238: public static final int REPAINT_SELECTION_ON_ACTIVATION_CHANGE = 16;
0239: /** Repaint policy bitmask indicating that all tabs should be repainted when the activated state changes */
0240: public static final int REPAINT_ALL_TABS_ON_ACTIVATION_CHANGE = 32; //includes selection
0241: /** Repaint policy bitmask indicating that a tab should be repainted when it becomes selected/unselected */
0242: public static final int REPAINT_ON_SELECTION_CHANGE = 64;
0243: /** Repaint policy bitmask indicating that all tabs should be repainted whenever the selection changes */
0244: public static final int REPAINT_ALL_TABS_ON_SELECTION_CHANGE = 128;
0245: /** Repaint policy bitmask indicating that the tab should be repainted when the close button is pressed */
0246: public static final int REPAINT_ON_CLOSE_BUTTON_PRESSED = 256;
0247:
0248: /**
0249: * Get the state of a given tab. Subclasses are expected to override this
0250: * to provide information about states such as clipping which can only be
0251: * found out via layout information, such as LEFTMOST, RIGHTMOST, CLIP_LEFT
0252: * and CLIP_RIGHT. The state is used by tab renderers to determine how they
0253: * should paint themselves.
0254: *
0255: * @param tab The index of the tab
0256: * @return The state
0257: */
0258: public int getState(int tab) {
0259: int result = 0;
0260: if (tab == pressedIndex) {
0261: result |= PRESSED;
0262: }
0263: if (tab == containsMouseIndex) {
0264: result |= ARMED;
0265: }
0266: if (tab == closeButtonContainsMouseIndex) {
0267: result |= CLOSE_BUTTON_ARMED;
0268: }
0269: if (tab == mousePressedInCloseButtonIndex) {
0270: result |= MOUSE_PRESSED_IN_CLOSE_BUTTON;
0271: }
0272: if (mouseInTabsArea) {
0273: result |= MOUSE_IN_TABS_AREA;
0274: }
0275: if (active) {
0276: result |= ACTIVE;
0277: }
0278: if (tab == selectedIndex) {
0279: result |= SELECTED;
0280: }
0281: if (tab != 0 && tab == selectedIndex + 1) {
0282: result |= AFTER_SELECTED;
0283: }
0284: if (tab == selectedIndex - 1) {
0285: result |= BEFORE_SELECTED;
0286: }
0287: if (tab == containsMouseIndex - 1) {
0288: result |= BEFORE_ARMED;
0289: }
0290: if (isAlarmTab(tab)) {
0291: result |= ATTENTION;
0292: }
0293: return result;
0294: }
0295:
0296: /** For debugging, enables fetching tab state as a string */
0297: String getStateString(int tab) {
0298: return stateToString(getState(tab));
0299: }
0300:
0301: /**
0302: * Clear all mouse position related state information. This should be done
0303: * following events in the model that alter the state sufficiently that the
0304: * cached information is probably wrong.
0305: */
0306: public void clearTransientStates() {
0307: pressedIndex = -1;
0308: containsMouseIndex = -1;
0309: closeButtonContainsMouseIndex = -1;
0310: mousePressedInCloseButtonIndex = -1;
0311: mouseInTabsArea = false;
0312: lastChangeType = NO_CHANGE;
0313: lastChange = 0;
0314: prev = -1;
0315: curr = -1;
0316: }
0317:
0318: /**
0319: * Set the index of the tab over which a mouse button has been pressed.
0320: *
0321: * @param i The tab which is pressed, or -1 to clear PRESSED from the state
0322: * of the previously pressed tab
0323: * @return Index of the tab which previously had the state PRESSED, or -1
0324: */
0325: public final int setPressed(int i) {
0326: prev = pressedIndex;
0327: pressedIndex = i;
0328: curr = i;
0329: possibleChange(prev, curr, PRESSED);
0330: return prev;
0331: }
0332:
0333: /**
0334: * Set the index of the tab which currently contains the mouse cursor.
0335: *
0336: * @param i The tab which contains the mouse cursor, or -1 to clear ARMED
0337: * from the state of the tab
0338: * @return Index of the tab which previously had the state ARMED, or -1
0339: */
0340: public final int setContainsMouse(int i) {
0341: prev = containsMouseIndex;
0342: containsMouseIndex = i;
0343: curr = i;
0344: possibleChange(prev, curr, ARMED);
0345: return prev;
0346: }
0347:
0348: /**
0349: * Set the index of the tab whose close button contains the mouse cursor.
0350: *
0351: * @param i The index of the tab whose close button contains the mouse
0352: * cursor, or -1 to clear CLOSE_BUTTON_CONTAINS_MOUSE from the
0353: * state of the tab which previously had it
0354: * @return Index of the tab which formerly had the state
0355: * CLOSE_BUTTON_CONTAINS_MOUSE, or -1
0356: */
0357: public final int setCloseButtonContainsMouse(int i) {
0358: prev = closeButtonContainsMouseIndex;
0359: closeButtonContainsMouseIndex = i;
0360: curr = i;
0361: possibleChange(prev, curr, CLOSE_BUTTON_ARMED);
0362: return prev;
0363: }
0364:
0365: /**
0366: * Set the index of the tab in which the mouse button has been pressed in
0367: * the close button. This is distinct from the combination of
0368: * CLOSE_BUTTON_ARMED and PRESSED, since the user may press the close button
0369: * and then drag away from the close button (clearing the CLOSE_BUTTON_ARMED
0370: * state) to abort closing a tab.
0371: *
0372: * @param i The tab in which the mouse was pressed while over the close
0373: * button
0374: * @return Index of the tab which previously had the state
0375: * MOUSE_PRESSED_IN_CLOSE_BUTTON, or -1
0376: */
0377: public final int setMousePressedInCloseButton(int i) {
0378: prev = mousePressedInCloseButtonIndex;
0379: mousePressedInCloseButtonIndex = i;
0380: curr = i;
0381: possibleChange(prev, curr, MOUSE_PRESSED_IN_CLOSE_BUTTON);
0382: return prev;
0383: }
0384:
0385: /**
0386: * Set the index of the tab which is currently selected. Note that users of
0387: * this class must ensure that this value stays up to date when changes
0388: * occur in the model such as inserting tabs before the selected one.
0389: *
0390: * @param i The tab index which is selected
0391: * @return Index of the tab which previously was selected, or -1 if none
0392: */
0393: public final int setSelected(int i) {
0394: prev = selectedIndex;
0395: selectedIndex = i;
0396: curr = i;
0397: removeAlarmTab(i);
0398: possibleChange(prev, curr, SELECTED);
0399: return prev;
0400: }
0401:
0402: /**
0403: * Set the condition for all tabs of the mouse being in the tabs area.
0404: *
0405: * @param b Whether the mouse is in the tabs area or not
0406: * @return The previous state with regard to the mouse being in the tabs
0407: * area
0408: */
0409: public final boolean setMouseInTabsArea(boolean b) {
0410: boolean prev = mouseInTabsArea;
0411: mouseInTabsArea = b;
0412: possibleChange(prev, b, MOUSE_IN_TABS_AREA);
0413: return prev;
0414: }
0415:
0416: /**
0417: * Set the condition for all tabs of the component being activated.
0418: *
0419: * @param b Whether or not the component is activated
0420: * @return The previous state with regard to the component being activated
0421: */
0422: public final boolean setActive(boolean b) {
0423: boolean prev = active;
0424: active = b;
0425: possibleChange(prev, b, ACTIVE);
0426: removeAlarmTab(selectedIndex);
0427: return prev;
0428: }
0429:
0430: private boolean isAlarmTab(int tab) {
0431: return attentionToggle && alarmTabs.contains(new Integer(tab));
0432: }
0433:
0434: private final HashSet<Integer> alarmTabs = new HashSet<Integer>(6);
0435:
0436: /** Add a tab to the list of those which should "flash" or otherwise give
0437: * some notification to the user to get their attention */
0438: public final void addAlarmTab(int alarmTab) {
0439: Integer in = new Integer(alarmTab);
0440: boolean added = alarmTabs.contains(in);
0441: boolean wasEmpty = alarmTabs.isEmpty();
0442: if (!added) {
0443: alarmTabs.add(new Integer(alarmTab));
0444: repaintTab(alarmTab);
0445: }
0446: if (wasEmpty) {
0447: startAlarmTimer();
0448: attentionToggle = true;
0449: repaintTab(alarmTab);
0450: }
0451: }
0452:
0453: /** Remove a tab to the list of those which should "flash" or otherwise give
0454: * some notification to the user to get their attention */
0455: public final void removeAlarmTab(int alarmTab) {
0456: Integer in = new Integer(alarmTab);
0457: boolean contained = alarmTabs.contains(in);
0458: if (contained) {
0459: alarmTabs.remove(in);
0460: boolean empty = alarmTabs.isEmpty();
0461: boolean wasAttentionToggled = attentionToggle;
0462: if (alarmTabs.isEmpty()) {
0463: stopAlarmTimer();
0464: }
0465: if (wasAttentionToggled) {
0466: repaintTab(alarmTab);
0467: }
0468: }
0469: }
0470:
0471: private Timer alarmTimer = null;
0472: private boolean attentionToggle = false;
0473:
0474: private final void startAlarmTimer() {
0475: if (alarmTimer == null) {
0476: ActionListener al = new ActionListener() {
0477: public void actionPerformed(ActionEvent ae) {
0478: attentionToggle = !attentionToggle;
0479: Timer timer = (Timer) ae.getSource();
0480: for (Iterator i = alarmTabs.iterator(); i.hasNext();) {
0481: repaintTab(((Integer) i.next()).intValue());
0482: }
0483: }
0484: };
0485: alarmTimer = new Timer(700, al);
0486: alarmTimer.setRepeats(true);
0487: }
0488: alarmTimer.start();
0489: }
0490:
0491: private final void stopAlarmTimer() {
0492: if (alarmTimer != null && alarmTimer.isRunning()) {
0493: alarmTimer.stop();
0494: attentionToggle = false;
0495: repaintAllTabs(); //XXX optimize
0496: }
0497: }
0498:
0499: boolean hasAlarmTabs() {
0500: return alarmTabs != null && !alarmTabs.isEmpty();
0501: }
0502:
0503: void pruneAlarmTabs(int max) {
0504: if (!hasAlarmTabs()) {
0505: return;
0506: }
0507: for (Iterator i = alarmTabs.iterator(); i.hasNext();) {
0508: if (((Integer) i.next()).intValue() >= max) {
0509: i.remove();
0510: }
0511: }
0512: if (alarmTabs.isEmpty()) {
0513: stopAlarmTimer();
0514: }
0515: }
0516:
0517: int[] getAlarmTabs() {
0518: int[] alarms = (int[]) Utilities
0519: .toPrimitiveArray((Integer[]) alarmTabs
0520: .toArray(new Integer[0]));
0521: Arrays.sort(alarms);
0522: return alarms;
0523: }
0524:
0525: //Handling of insertions/deletions where we'll need to update the
0526: //list of blinking tabs here.
0527: void intervalAdded(ListDataEvent evt) {
0528: if (!hasAlarmTabs())
0529: return;
0530: int start = evt.getIndex0();
0531: int end = evt.getIndex1();
0532: int[] alarms = (int[]) Utilities
0533: .toPrimitiveArray((Integer[]) alarmTabs
0534: .toArray(new Integer[0]));
0535: boolean changed = false;
0536: for (int i = 0; i < alarms.length; i++) {
0537: if (alarms[i] >= start) {
0538: alarms[i] += (end - start) + 1;
0539: changed = true;
0540: }
0541: }
0542: if (changed) {
0543: alarmTabs.clear();
0544: for (int i = 0; i < alarms.length; i++) {
0545: addAlarmTab(alarms[i]);
0546: }
0547: }
0548: }
0549:
0550: void intervalRemoved(ListDataEvent evt) {
0551: if (!hasAlarmTabs())
0552: return;
0553: int start = evt.getIndex0();
0554: int end = evt.getIndex1();
0555:
0556: int[] alarms = (int[]) Utilities
0557: .toPrimitiveArray((Integer[]) alarmTabs
0558: .toArray(new Integer[0]));
0559: Arrays.sort(alarms);
0560:
0561: if (end == start) {
0562: //Faster to handle this case separately
0563: boolean changed = true;
0564: for (int i = 0; i < alarms.length; i++) {
0565: if (alarms[i] > end) {
0566: alarms[i]--;
0567: } else if (alarms[i] == end) {
0568: alarms[i] = -1;
0569: }
0570: }
0571: if (changed) {
0572: alarmTabs.clear();
0573: boolean added = false;
0574: for (int i = 0; i < alarms.length; i++) {
0575: if (alarms[i] != -1) {
0576: addAlarmTab(alarms[i]);
0577: added = true;
0578: }
0579: }
0580: if (!added) {
0581: stopAlarmTimer();
0582: }
0583: }
0584: return;
0585: }
0586:
0587: boolean changed = false;
0588: for (int i = 0; i < alarms.length; i++) {
0589: if (alarms[i] >= start && alarms[i] <= end) {
0590: alarms[i] = -1;
0591: changed = true;
0592: }
0593: }
0594: for (int i = 0; i < alarms.length; i++) {
0595: if (alarms[i] > end) {
0596: alarms[i] -= (end - start) + 1;
0597: changed = true;
0598: }
0599: }
0600: if (changed) {
0601: alarmTabs.clear();
0602: boolean added = false;
0603: for (int i = 0; i < alarms.length; i++) {
0604: if (alarms[i] != -1) {
0605: addAlarmTab(alarms[i]);
0606: added = true;
0607: }
0608: }
0609: if (!added) {
0610: stopAlarmTimer();
0611: }
0612: }
0613: }
0614:
0615: void indicesAdded(ComplexListDataEvent e) {
0616: if (!hasAlarmTabs())
0617: return;
0618: int[] alarms = (int[]) Utilities
0619: .toPrimitiveArray((Integer[]) alarmTabs
0620: .toArray(new Integer[0]));
0621: java.util.Arrays.sort(alarms);
0622:
0623: int[] indices = e.getIndices();
0624: java.util.Arrays.sort(indices);
0625:
0626: boolean changed = false;
0627: for (int i = 0; i < indices.length; i++) {
0628: for (int j = 0; j < alarms.length; j++) {
0629: if (alarms[j] >= indices[i]) {
0630: alarms[j]++;
0631: changed = true;
0632: }
0633: }
0634: }
0635: if (changed) {
0636: alarmTabs.clear();
0637: for (int i = 0; i < alarms.length; i++) {
0638: if (alarms[i] != -1) {
0639: addAlarmTab(alarms[i]);
0640: }
0641: }
0642: }
0643: }
0644:
0645: void indicesRemoved(ComplexListDataEvent e) {
0646: if (!hasAlarmTabs())
0647: return;
0648: int[] indices = e.getIndices();
0649: java.util.Arrays.sort(indices);
0650:
0651: int[] alarms = (int[]) Utilities
0652: .toPrimitiveArray((Integer[]) alarmTabs
0653: .toArray(new Integer[0]));
0654: java.util.Arrays.sort(alarms);
0655:
0656: if (alarms[alarms.length - 1] < indices[0]) {
0657: //Some tab removed after the last blinking tab, don't care
0658: return;
0659: }
0660:
0661: boolean changed = false;
0662: for (int i = 0; i < alarms.length; i++) {
0663: //First weed out all deleted alarm tabs
0664: for (int j = 0; j < indices.length; j++) {
0665: if (alarms[i] == indices[j]) {
0666: alarms[i] = -1;
0667: changed = true;
0668: }
0669: }
0670: }
0671: for (int i = 0; i < alarms.length; i++) {
0672: //Now decrement those that remain that are affected
0673: int alarm = alarms[i];
0674: for (int j = 0; j < indices.length; j++) {
0675: if (alarm > indices[j]) {
0676: alarms[i]--;
0677: changed = true;
0678: }
0679: }
0680: }
0681:
0682: if (changed) {
0683: alarmTabs.clear();
0684: boolean addedSome = false;
0685: for (int i = 0; i < alarms.length; i++) {
0686: if (alarms[i] >= 0) {
0687: addAlarmTab(alarms[i]);
0688: addedSome = true;
0689: }
0690: }
0691: if (!addedSome) {
0692: stopAlarmTimer();
0693: }
0694: }
0695:
0696: repaintAllTabs();
0697: }
0698:
0699: void indicesChanged(ComplexListDataEvent e) {
0700: if (!hasAlarmTabs())
0701: return;
0702: if (e instanceof VeryComplexListDataEvent) { //it always will be
0703: VeryComplexListDataEvent ve = (VeryComplexListDataEvent) e;
0704:
0705: ArrayDiff dif = ((VeryComplexListDataEvent) e).getDiff();
0706:
0707: List old = Arrays.asList(dif.getOldData());
0708: List nue = Arrays.asList(dif.getNewData());
0709:
0710: int[] alarms = (int[]) Utilities
0711: .toPrimitiveArray((Integer[]) alarmTabs
0712: .toArray(new Integer[0]));
0713:
0714: boolean changed = false;
0715: for (int i = 0; i < alarms.length; i++) {
0716: Object o = old.get(alarms[i]);
0717: int idx = nue.indexOf(o);
0718: changed |= idx != alarms[i];
0719: alarms[i] = nue.indexOf(o);
0720: }
0721: if (changed) {
0722: alarmTabs.clear();
0723: boolean addedSome = false;
0724: for (int i = 0; i < alarms.length; i++) {
0725: if (alarms[i] >= 0) {
0726: addAlarmTab(alarms[i]);
0727: addedSome = true;
0728: }
0729: }
0730: if (!addedSome) {
0731: stopAlarmTimer();
0732: }
0733: }
0734: }
0735: }
0736:
0737: void contentsChanged(ListDataEvent evt) {
0738: if (!hasAlarmTabs())
0739: return;
0740: //Do nothing, just means some text or icons changed
0741: }
0742:
0743: //Change types
0744: /** Change type indicating no change happened (i.e. calling setSelected() with the same value it was previously
0745: * called with).
0746: */
0747: public static final int NO_CHANGE = 0;
0748: /** Change type indicating a change of state for two tabs */
0749: public static final int CHANGE_TAB_TO_TAB = 1;
0750: /** Change type indicating a change happened (such as the mouse leaving a tab) such that now no tab has the
0751: * state previously held by the affected tab */
0752: public static final int CHANGE_TAB_TO_NONE = 2;
0753: /** Change type indicating that a state was added that no tab previously had */
0754: public static final int CHANGE_NONE_TO_TAB = 3;
0755: public static final int CHANGE_TAB_TO_SELF = 4;
0756: /** Change type indicating one of the boolean state changes, such as STATE_ACTIVE */
0757: public static final int ALL_TABS = Integer.MAX_VALUE;
0758:
0759: protected void possibleChange(boolean prevVal, boolean currVal,
0760: int type) {
0761: if (prevVal == currVal) {
0762: lastChangeType = NO_CHANGE;
0763: } else {
0764: lastChangeType = ALL_TABS;
0765: }
0766: if (lastChangeType != NO_CHANGE) {
0767: lastAffected = ALL_TABS;
0768: change(ALL_TABS, ALL_TABS, type, lastChangeType);
0769: }
0770: }
0771:
0772: protected void possibleChange(int lastTab, int currTab, int type) {
0773: if (lastTab == currTab) {
0774: lastChangeType = NO_CHANGE;
0775: } else {
0776: if (currTab == -1) {
0777: lastChangeType = CHANGE_TAB_TO_NONE;
0778: } else if (lastTab == -1) {
0779: lastChangeType = CHANGE_NONE_TO_TAB;
0780: } else {
0781: lastChangeType = CHANGE_TAB_TO_TAB;
0782: }
0783: }
0784: if (lastChangeType != NO_CHANGE) {
0785: lastAffected = currTab;
0786: change(lastTab, currTab, type, lastChangeType);
0787: }
0788: }
0789:
0790: public String toString() {
0791: StringBuffer sb = new StringBuffer(50);
0792: sb.append("TabState [lastTab=");
0793: sb.append(tabToString(prev));
0794: sb.append(" currTab=");
0795: sb.append(tabToString(curr));
0796: sb.append(" lastAffected=");
0797: sb.append(tabToString(lastAffected));
0798: sb.append(" lastChangeType=");
0799: sb.append(changeToString(lastChangeType));
0800: sb.append(" lastChange=");
0801: sb.append(stateToString(lastChange));
0802: sb.append(" <active=");
0803: sb.append(active);
0804: sb.append(" sel=");
0805: sb.append(tabToString(selectedIndex));
0806: sb.append(" mouse=");
0807: sb.append(tabToString(containsMouseIndex));
0808: sb.append(" inTabs=");
0809: sb.append(mouseInTabsArea);
0810: sb.append(" pressed=");
0811: sb.append(tabToString(pressedIndex));
0812: sb.append(" inCloseButton=");
0813: sb.append(tabToString(closeButtonContainsMouseIndex));
0814: sb.append(" pressedCloseButton=");
0815: sb.append(tabToString(mousePressedInCloseButtonIndex));
0816: sb.append(">]");
0817: return sb.toString();
0818: }
0819:
0820: /**
0821: * Called when a setter for a tab index has produced a change in a
0822: * state-affecting property, such as which tab contains the mouse. Fetches
0823: * the repaint policies, and if the change is one that the policy says
0824: * should produce a repaint, calls repaintTab for the appropriate tabs.
0825: *
0826: * @param lastTab The tab previously holding the state which has changed,
0827: * or -1
0828: * @param currTab The tab currently holding the state which has changed,
0829: * or -1
0830: * @param type The thing that changed. This will be one of the state
0831: * constants.
0832: * @param changeType This is one of the defined change types such as
0833: * ALL_TABS, TAB_TO_TAB, etc.
0834: */
0835: protected void change(int lastTab, int currTab, int type,
0836: int changeType) {
0837: lastChange = type;
0838: // System.err.println("Change-type: " + stateToString(type) + " - " + changeToString (changeType) + " from " + tabToString (lastTab) + " to " + tabToString (currTab));
0839: if (changeType == CHANGE_TAB_TO_TAB) {
0840: maybeRepaint(lastTab, type);
0841: } else if (changeType == CHANGE_TAB_TO_NONE) {
0842: maybeRepaint(lastTab, type);
0843: return;
0844: } else if (changeType == ALL_TABS
0845: && (getRepaintPolicy(currTab) & REPAINT_ALL_ON_MOUSE_ENTER_TABS_AREA) != 0) {
0846: repaintAllTabs();
0847: return;
0848: }
0849: maybeRepaint(currTab, type);
0850: }
0851:
0852: protected void maybeRepaint(int tab, int type) {
0853: int rpol = getRepaintPolicy(tab);
0854: boolean go = false;
0855: switch (type) {
0856: case ACTIVE:
0857: go = (rpol & REPAINT_SELECTION_ON_ACTIVATION_CHANGE) != 0;
0858: if ((rpol & REPAINT_ALL_TABS_ON_ACTIVATION_CHANGE) != 0) {
0859: type = ALL_TABS;
0860: go = true;
0861: }
0862: break;
0863: case ARMED:
0864: go = (rpol & REPAINT_ON_MOUSE_ENTER_TAB) != 0
0865: || tab == closeButtonContainsMouseIndex;
0866: closeButtonContainsMouseIndex = -1;
0867: break;
0868: case CLOSE_BUTTON_ARMED:
0869: go = (rpol & REPAINT_ON_MOUSE_ENTER_CLOSE_BUTTON) != 0;
0870: break;
0871: case MOUSE_IN_TABS_AREA:
0872: go = (rpol & REPAINT_ALL_ON_MOUSE_ENTER_TABS_AREA) != 0;
0873: break;
0874: case MOUSE_PRESSED_IN_CLOSE_BUTTON:
0875: go = (rpol & REPAINT_ON_CLOSE_BUTTON_PRESSED) != 0;
0876: break;
0877: case PRESSED:
0878: go = (rpol & REPAINT_ON_MOUSE_PRESSED) != 0;
0879: break;
0880: case SELECTED:
0881: go = (rpol & REPAINT_ON_SELECTION_CHANGE) != 0;
0882: if ((rpol & REPAINT_ALL_TABS_ON_SELECTION_CHANGE) != 0) {
0883: type = ALL_TABS;
0884: go = true;
0885: }
0886: break;
0887: case ATTENTION:
0888: go = true;
0889: }
0890: if (go) {
0891: if (type == ALL_TABS) {
0892: repaintAllTabs();
0893: } else {
0894: repaintTab(tab);
0895: }
0896: }
0897: }
0898:
0899: protected abstract void repaintTab(int tab);
0900:
0901: protected abstract void repaintAllTabs();
0902:
0903: static final String changeToString(int change) {
0904: switch (change) {
0905: case NO_CHANGE:
0906: return "no change"; //NOI18N
0907: case CHANGE_TAB_TO_TAB:
0908: return "tab to tab"; //NOI18N
0909: case CHANGE_TAB_TO_NONE:
0910: return "tab to none"; //NOI18N
0911: case CHANGE_NONE_TO_TAB:
0912: return "none to tab"; //NOI18N
0913: case CHANGE_TAB_TO_SELF:
0914: return "tab to self"; //NOI18N
0915: case ALL_TABS:
0916: return "all tabs"; //NOI18N
0917: default:
0918: return "??? " + change; //NOI18N
0919: }
0920: }
0921:
0922: static final String tabToString(int tab) {
0923: if (tab == ALL_TABS) {
0924: return "all tabs"; //NOI18N
0925: } else if (tab == -1) {
0926: return "none"; //NOI18N
0927: } else {
0928: return Integer.toString(tab);
0929: }
0930: }
0931:
0932: /**
0933: * Static utility method to get a string representation of a state
0934: */
0935: static final String stateToString(int st) {
0936: String[] states = new String[] { "clip right", "clip left",
0937: "armed", "pressed", "selected", "active",
0938: "not onscreen",
0939: "leftmost", //NOI18N
0940: "rightmost", "in closebutton", "before selected",
0941: "after selected", "mouse in tabs area", //NOI18N
0942: "mouse pressed in close button" //NOI18N
0943: }; //NOI18N
0944: int[] vals = new int[] { CLIP_RIGHT, CLIP_LEFT, ARMED, PRESSED,
0945: SELECTED, ACTIVE, NOT_ONSCREEN, LEFTMOST, RIGHTMOST,
0946: CLOSE_BUTTON_ARMED, BEFORE_SELECTED, AFTER_SELECTED,
0947: MOUSE_IN_TABS_AREA, MOUSE_PRESSED_IN_CLOSE_BUTTON };
0948: StringBuffer sb = new StringBuffer();
0949: for (int i = 0; i < vals.length; i++) {
0950: if ((st & vals[i]) != 0) {
0951: if (sb.length() > 0) {
0952: sb.append(',');
0953: }
0954: sb.append(states[i]);
0955: }
0956: }
0957: if (sb.length() == 0) {
0958: sb.append("no flags set"); //NOI18N
0959: }
0960: sb.append("=");
0961: sb.append(st);
0962: return sb.toString();
0963: }
0964:
0965: static String repaintPolicyToString(int policy) {
0966: if (policy == 0) {
0967: return "repaint nothing";
0968: }
0969: String[] names = new String[] { "REPAINT_ON_MOUSE_ENTER_TAB",
0970: "REPAINT_ALL_ON_MOUSE_ENTER_TABS_AREA",
0971: "REPAINT_ON_MOUSE_ENTER_CLOSE_BUTTON",
0972: "REPAINT_ON_MOUSE_PRESSED",
0973: "REPAINT_SELECTION_ON_ACTIVATION_CHANGE",
0974: "REPAINT_ALL_TABS_ON_ACTIVATION_CHANGE",
0975: "REPAINT_ON_SELECTION_CHANGE",
0976: "REPAINT_ALL_TABS_ON_SELECTION_CHANGE",
0977: "REPAINT_ON_CLOSE_BUTTON_PRESSED", };
0978: int[] vals = new int[] { REPAINT_ON_MOUSE_ENTER_TAB,
0979: REPAINT_ALL_ON_MOUSE_ENTER_TABS_AREA,
0980: REPAINT_ON_MOUSE_ENTER_CLOSE_BUTTON,
0981: REPAINT_ON_MOUSE_PRESSED,
0982: REPAINT_SELECTION_ON_ACTIVATION_CHANGE,
0983: REPAINT_ALL_TABS_ON_ACTIVATION_CHANGE,
0984: REPAINT_ON_SELECTION_CHANGE,
0985: REPAINT_ALL_TABS_ON_SELECTION_CHANGE,
0986: REPAINT_ON_CLOSE_BUTTON_PRESSED, };
0987: StringBuffer sb = new StringBuffer();
0988: for (int i = 0; i < vals.length; i++) {
0989: if ((policy & vals[i]) != 0) {
0990: sb.append(names[i]);
0991: if (i != vals.length - 1) {
0992: sb.append('+');
0993: }
0994: }
0995: }
0996: return sb.toString();
0997: }
0998:
0999: /**
1000: * Get the repaint policy that will be used to determine what tabs to repaint, based on state changes.
1001: * The default implementation in BasicTabDisplayerUI simply ignores the tab argument and returns a
1002: * single policy for all tabs created in <code>BasicTabDisplayerUI.createRepaintPolicy()</code>
1003: *
1004: * @param tab Index of tab in question
1005: * @return Type of repaint policy for given tab
1006: */
1007: public abstract int getRepaintPolicy(int tab);
1008: }
|