Index: core/swing/tabcontrol/demosrc/org/netbeans/swing/tabcontrol/demo/TestFrame.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/demosrc/org/netbeans/swing/tabcontrol/demo/TestFrame.java,v --- core/swing/tabcontrol/demosrc/org/netbeans/swing/tabcontrol/demo/TestFrame.java 10 Aug 2004 13:56:37 -0000 1.5 +++ core/swing/tabcontrol/demosrc/org/netbeans/swing/tabcontrol/demo/TestFrame.java 10 Sep 2004 18:54:30 -0000 @@ -63,10 +63,16 @@ */ try { - //UIManager.setLookAndFeel(new javax.swing.plaf.metal.MetalLookAndFeel()); +// UIManager.setLookAndFeel(new javax.swing.plaf.metal.MetalLookAndFeel()); } catch (Exception e) { } +// UIManager.put ("EditorTabDisplayerUI", "org.netbeans.swing.tabcontrol.plaf.WinClassicEditorTabDisplayerUI"); +// UIManager.put ("ViewTabDisplayerUI", "org.netbeans.swing.tabcontrol.plaf.WinClassicViewTabDisplayerUI"); +// UIManager.put ("EditorTabDisplayerUI", "org.netbeans.swing.tabcontrol.plaf.WinXPEditorTabDisplayerUI"); +// UIManager.put ("ViewTabDisplayerUI", "org.netbeans.swing.tabcontrol.plaf.WinXPViewTabDisplayerUI"); + + JLabel jb1 = new JLabel("Label 1"); final JButton jb2 = new JButton("Button 2 - Update UI"); JButton jb3 = new JButton("Click me to remove this tab"); @@ -90,6 +96,14 @@ JTA.setColumns(80); JTA.setLineWrap(true); + JButton jb7 = new JButton ("Remove non-contiguous tabs"); + JButton jb8 = new JButton ("Discontig add"); + + JButton jb9 = new JButton ("Contig add"); + JButton jb10 = new JButton ("Contig remove"); + + JButton jb11 = new JButton ("Discontig add and remove"); + TabData tab0 = new TabData(jtr, myIcon, "0 JTree", "0"); TabData tab1 = new TabData(jb1, myIcon, "1 Tab 1", "1"); @@ -107,17 +121,17 @@ "7"); TabData tab8 = new TabData(new JLabel("gioo"), myIcon, "8 something", "8"); - TabData tab9 = new TabData(new JButton("foo"), myIcon, "9 foob", + TabData tab9 = new TabData(jb7, myIcon, "9 Discontig remove", "9"); TabData tab10 = new TabData(new JLabel("gioo"), myIcon, "10 wiggle", "10"); - TabData tab11 = new TabData(new JButton("foo"), myIcon, "11 bumble", + TabData tab11 = new TabData(jb8, myIcon, "11 Discontig add", "11"); - TabData tab12 = new TabData(new JLabel("mooble"), myIcon, - "12 poodle", "12"); - TabData tab13 = new TabData(new JButton("fooble"), myIcon, - "13 hoover", "13"); - TabData tab14 = new TabData(new JLabel("gooble"), myIcon, "14 snip", + TabData tab12 = new TabData(jb9, myIcon, + "12 contig add", "12"); + TabData tab13 = new TabData(jb10, myIcon, + "13 contig remove", "13"); + TabData tab14 = new TabData(jb11, myIcon, "14 MUNGE", "14"); TabDataModel mdl = new DefaultTabDataModel(new TabData[]{ @@ -130,8 +144,10 @@ ); */ - final TabbedContainer tab = new TabbedContainer(mdl, TabbedContainer.TYPE_EDITOR); - tab.setActive(true); + final TabbedContainer tab = new TabbedContainer(mdl, TabbedContainer.TYPE_VIEW); + tab.setActive(true); + tab.requestAttention(5); + tab.requestAttention(3); jb6.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { @@ -161,6 +177,72 @@ tab.updateUI(); } }); + + jb7.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ae) { + tab.getModel().removeTabs(new int[] {1, 3, 7, 8}); + } + }); + + jb8.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ae) { + int[] idxs = new int [] { 1, 3, 6, 8}; + TabData[] td = new TabData[] { + new TabData(new JButton("inserted 1"), myIcon, "I-1", "tip"), + new TabData(new JButton("inserted 3"), myIcon, "I-3", "tip"), + new TabData(new JButton("inserted 6"), myIcon, "I-6", "tip"), + new TabData(new JButton("inserted 8"), myIcon, "I-8", "tip"), + + }; + tab.getModel().addTabs(idxs, td); + } + }); + + jb9.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ae) { + int[] idxs = new int [] { 1, 3, 6, 8}; + TabData[] td = new TabData[] { + new TabData(new JButton("inserted c 1"), myIcon, "Ic-1", "tip"), + new TabData(new JButton("inserted c 2"), myIcon, "Ic-2", "tip"), + new TabData(new JButton("inserted c 3"), myIcon, "Ic-3", "tip"), + new TabData(new JButton("inserted c 4"), myIcon, "Ic-4", "tip"), + + }; + tab.getModel().addTabs(1, td); + } + }); + + jb10.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ae) { + tab.getModel().removeTabs(1, 4); + } + }); + + jb11.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ae) { + TabData[] data = (TabData[]) tab.getModel().getTabs().toArray(new TabData[0]); + + TabData[] newData = new TabData [ data.length - 3]; + int ct = 0; + + //strip out some tabs + for (int i=0; i < data.length; i++) { + newData[ct] = data[i]; + if (i != 2 && i != 3 && i != 7) { + ct++; + } + } + + //replace one tab + newData[1] = new TabData (new JLabel ("Hi there"), myIcon, "New tab", "foo"); + //swap some tabs + TabData td = newData[8]; + newData[8] = newData[7]; + newData[7] = td; + + tab.getModel().setTabs(newData); + } + }); tab.setActive(true); Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/DefaultTabDataModel.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/DefaultTabDataModel.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/DefaultTabDataModel.java 22 Apr 2004 13:09:06 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/DefaultTabDataModel.java 10 Sep 2004 18:54:30 -0000 @@ -327,9 +327,17 @@ fireIntervalRemoved(lde); } + /** + * Remove a range of tabs from start up to and including + * finish. + */ public void removeTabs(int start, int end) { java.util.List affected = list.subList(start, end); - list.removeRange(start, end); + if (start == end) { + list.remove(start); + } else { + list.removeRange(start, end + 1); + } ComplexListDataEvent lde = new ComplexListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, start, end); @@ -343,7 +351,6 @@ m.put(new Integer(indices[i]), data[i]); } Arrays.sort(indices); -// for (int i=indices.length-1; i >= 0; i--) { for (int i = 0; i < indices.length; i++) { Integer key = new Integer(indices[i]); TabData currData = (TabData) m.get(key); Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/SlidingButton.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/SlidingButton.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/SlidingButton.java 7 Jul 2004 07:31:19 -0000 1.4 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/SlidingButton.java 10 Sep 2004 18:54:30 -0000 @@ -12,10 +12,12 @@ */ package org.netbeans.swing.tabcontrol; +import java.awt.Color; import java.awt.Component; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import javax.swing.Timer; import javax.swing.JToggleButton; import javax.swing.SwingConstants; import javax.swing.UIManager; @@ -42,6 +44,8 @@ private int orientation; /** Asociated component which actually slides from this button */ private Component component; + + private TabData data; /** Create a new button representing TabData from the model. * @@ -66,7 +70,7 @@ setBorderPainted(false); // setHorizontalTextPosition(SwingConstants.); // setVerticalTextPosition(SwingConstants.CENTER); - + this.data = buttonData; // note, updateUI() is called from superclass constructor } @@ -77,6 +81,7 @@ public void removeNotify() { super.removeNotify(); + setBlinking(false); //XXX register with tooltip manager } @@ -100,6 +105,53 @@ /** Returns orinetation of this button */ public int getOrientation() { return orientation; + } + + public boolean isBlinking() { + return blinkState; + } + + private Timer blinkTimer = null; + private boolean blinkState = false; + public void setBlinking (boolean val) { + if (!val && blinkTimer != null) { + blinkTimer.stop(); + blinkTimer = null; + boolean wasBlinkState = blinkState; + blinkState = false; + if (wasBlinkState) { + repaint(); + } + } else if (val && blinkTimer == null) { + blinkTimer = new Timer(700, new BlinkListener()); + blinkState = true; + blinkTimer.start(); + repaint(); + } + } + + private class BlinkListener implements ActionListener { + public void actionPerformed (ActionEvent ae) { + blinkState = !blinkState; + repaint(); + } + } + + /** + * Used by the UI to determine whether to use the blink + * color or the regular color + */ + public final boolean isBlinkState() { + return blinkState; + } + + public final Color getBackground() { + return isBlinkState() ? + new Color(252, 250, 244) : super.getBackground(); + } + + public TabData getData() { + return data; } } Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/SlidingButtonUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/SlidingButtonUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/SlidingButtonUI.java 2 Jun 2004 12:31:30 -0000 1.1 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/SlidingButtonUI.java 10 Sep 2004 18:54:31 -0000 @@ -210,7 +210,7 @@ Insets margin = b.getMargin(); g.fillRect(insets.left - margin.left, - insets.top - margin.top, + insets.top - margin.top, size.width - (insets.left-margin.left) - (insets.right - margin.right), size.height - (insets.top-margin.top) - (insets.bottom - margin.bottom)); } Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabDisplayer.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabDisplayer.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabDisplayer.java 23 Aug 2004 12:22:15 -0000 1.8 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabDisplayer.java 10 Sep 2004 18:54:31 -0000 @@ -333,6 +333,31 @@ public final Dimension getMinimumSize() { return getUI().getMinimumSize(this); } + + /** + * Cause the specified tab to flash or otherwise call attention to itself + * without changing selection or focus. Supported by VIEW and EDITOR type + * UIs. + */ + public final void requestAttention (int tab) { + getUI().requestAttention(tab); + } + + /** + * Cause a tab, if blinking, to stop. + */ + public final void cancelRequestAttention (int tab) { + getUI().cancelRequestAttention (tab); + } + + public final boolean requestAttention (TabData data) { + int idx = getModel().indexOf(data); + boolean result = idx >= 0; + if (result) { + requestAttention (idx); + } + return result; + } /** * Accessor only for TabDisplayerUI when installing the UI Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabDisplayerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabDisplayerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabDisplayerUI.java 10 Aug 2004 13:56:37 -0000 1.4 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabDisplayerUI.java 10 Sep 2004 18:54:32 -0000 @@ -184,5 +184,9 @@ public abstract void registerShortcuts (JComponent comp); public abstract void unregisterShortcuts (JComponent comp); + + protected abstract void requestAttention (int tab); + + protected abstract void cancelRequestAttention (int tab); } Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabbedContainer.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabbedContainer.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabbedContainer.java 11 Aug 2004 13:43:22 -0000 1.9 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabbedContainer.java 10 Sep 2004 18:54:33 -0000 @@ -556,6 +556,38 @@ firePropertyChange(PROP_ACTIVE, !active, active); } } + + /** + * Cause the tab at the specified index to blink or otherwise suggest that + * the user should click it. + */ + public final void requestAttention (int tab) { + getUI().requestAttention(tab); + } + + public final void cancelRequestAttention (int tab) { + getUI().cancelRequestAttention(tab); + } + + /** + * Cause the specified tab to blink or otherwisse suggest that the user should + * click it. + */ + public final boolean requestAttention (TabData data) { + int idx = getModel().indexOf(data); + boolean result = idx >= 0; + if (result) { + requestAttention (idx); + } + return result; + } + + public final void cancelRequestAttention (TabData data) { + int idx = getModel().indexOf(data); + if (idx != -1) { + cancelRequestAttention(idx); + } + } /** * Determine if this component thinks it is "active", which Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabbedContainerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabbedContainerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabbedContainerUI.java 10 Aug 2004 13:56:37 -0000 1.4 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabbedContainerUI.java 10 Sep 2004 18:54:33 -0000 @@ -199,4 +199,8 @@ */ public abstract int dropIndexOfPoint(Point p); + protected abstract void requestAttention (int tab); + + protected abstract void cancelRequestAttention (int tab); + } Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java 8 Sep 2004 22:48:58 -0000 1.4 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java 10 Sep 2004 18:54:33 -0000 @@ -190,6 +190,10 @@ protected final boolean isRightmost() { return (state & TabState.RIGHTMOST) != 0; } + + protected final boolean isAttention() { + return (state & TabState.ATTENTION) != 0; + } /** * Convenience getter to determine if the current state indicates Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractViewTabDisplayerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractViewTabDisplayerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractViewTabDisplayerUI.java 2 Sep 2004 13:43:09 -0000 1.15 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractViewTabDisplayerUI.java 10 Sep 2004 18:54:34 -0000 @@ -13,6 +13,7 @@ package org.netbeans.swing.tabcontrol.plaf; +import javax.swing.event.ListDataEvent; import org.netbeans.swing.tabcontrol.TabData; import org.netbeans.swing.tabcontrol.TabDataModel; import org.netbeans.swing.tabcontrol.TabDisplayer; @@ -38,6 +39,8 @@ import java.util.Map; import org.netbeans.swing.tabcontrol.LocationInformer; import org.netbeans.swing.tabcontrol.TabbedContainer; +import org.netbeans.swing.tabcontrol.event.ComplexListDataEvent; +import org.netbeans.swing.tabcontrol.event.ComplexListDataListener; /** * Basic UI class for view tabs - non scrollable tabbed displayer, which shows all @@ -83,6 +86,7 @@ dataModel = displayer.getModel(); layoutModel = new ViewTabLayoutModel(dataModel, displayer); dataModel.addChangeListener (controller); + dataModel.addComplexListDataListener(controller); displayer.addPropertyChangeListener (controller); selectionModel.addChangeListener (controller); displayer.addMouseListener(controller); @@ -106,6 +110,7 @@ ToolTipManager.sharedInstance().unregisterComponent(displayer); displayer.removePropertyChangeListener (controller); dataModel.removeChangeListener(controller); + dataModel.removeComplexListDataListener(controller); selectionModel.removeChangeListener(controller); displayer.removeMouseListener(controller); displayer.removeMouseMotionListener(controller); @@ -503,9 +508,12 @@ } public Rectangle getTabRect(int index, Rectangle destination) { + if (destination == null) { + destination = new Rectangle(); + } if (index < 0 || index > displayer.getModel().size()) { - throw new ArrayIndexOutOfBoundsException("Tab index out of " + - "bounds: " + index); + destination.setBounds (0,0,0,0); + return destination; } destination.x = layoutModel.getX(index); destination.width = layoutModel.getW(index); @@ -532,14 +540,64 @@ return -1; } + + protected int createRepaintPolicy () { + return TabState.REPAINT_SELECTION_ON_ACTIVATION_CHANGE + | TabState.REPAINT_ON_SELECTION_CHANGE + | TabState.REPAINT_ON_MOUSE_ENTER_TAB + | TabState.REPAINT_ON_MOUSE_ENTER_CLOSE_BUTTON + | TabState.REPAINT_ON_MOUSE_PRESSED; + } + + protected final TabState tabState = new ViewTabState(); + + private class ViewTabState extends TabState { + public int getRepaintPolicy(int tab) { + return createRepaintPolicy(); + } + + public void repaintAllTabs() { + displayer.repaint(); + } + + public void repaintTab (int tab) { + if (tab < 0 || tab > displayer.getModel().size()) { + //This can happen because we can be notified + //of a change on a tab that has just been removed + //from the model + return; + } + Rectangle r = getTabRect(tab, null); + displayer.repaint(r); + } + } + + /** + * Determine if the tab should be flashing + */ + protected boolean isAttention (int tab) { + return (tabState.getState(tab) & TabState.ATTENTION) != 0; + } + + + protected void requestAttention (int tab) { + tabState.addAlarmTab(tab); + } + + protected void cancelRequestAttention (int tab) { + tabState.removeAlarmTab(tab); + } /** * Listen to mouse events and handles selection behaviour and close icon * button behaviour. */ abstract class Controller extends MouseAdapter - implements MouseMotionListener, ChangeListener, PropertyChangeListener, ActionListener { + implements MouseMotionListener, ChangeListener, PropertyChangeListener, ActionListener, ComplexListDataListener { + //XXX should be able to replace most of this class with + //tabState - we're already using it to manage the blinking state + /** * index of tab whose close icon currently pressed, -1 otherwise */ @@ -596,11 +654,13 @@ public void mousePressed(MouseEvent e) { Point p = e.getPoint(); int i = getLayoutModel().indexOfPoint(p.x, p.y); + tabState.setPressed(i); SingleSelectionModel sel = getSelectionModel(); selectionChanged = i != sel.getSelectedIndex(); // invoke possible selection change if ((i != -1) || !selectionChanged) { getSelectionModel().setSelectedIndex(i); + tabState.setSelected(i); } // update pressed state if (shouldReact(e) && !selectionChanged) { @@ -632,6 +692,7 @@ public void mouseReleased(MouseEvent e) { // close button must not be active when selection change was // triggered by mouse press + tabState.setPressed(-1); if (shouldReact(e) && !selectionChanged) { setClosePressed(-1); Point point = e.getPoint(); @@ -721,6 +782,7 @@ // sync of indexes int oldValue = mouseInCloseButton; mouseInCloseButton = isNow; + tabState.setCloseButtonContainsMouse(isNow); if (isNow == -1) { // exit from close area TabLayoutModel tlm = getLayoutModel(); @@ -745,7 +807,44 @@ public void actionPerformed(ActionEvent e) { performPinAction(); } + + public void indicesAdded(ComplexListDataEvent e) { + tabState.indicesAdded(e); + } + + /** + * Elements have been removed at the indices specified by the event's + * getIndices() value + * + * @param e The event + */ + public void indicesRemoved(ComplexListDataEvent e) { + tabState.indicesRemoved(e); + } + + /** + * Elements have been changed at the indices specified by the event's + * getIndices() value. If the changed data can affect display width (such + * as a text change or a change in icon size), the event's + * isTextChanged() method will return true. + * + * @param e The event + */ + public void indicesChanged(ComplexListDataEvent e) { + tabState.indicesChanged(e); + } + public void intervalAdded (ListDataEvent evt) { + tabState.intervalAdded(evt); + } + + public void intervalRemoved (ListDataEvent evt) { + tabState.intervalRemoved(evt); + } + + public void contentsChanged(ListDataEvent evt) { + tabState.contentsChanged(evt); + } } // end of Controller Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java 15 Jun 2004 19:58:32 -0000 1.3 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java 10 Sep 2004 18:54:35 -0000 @@ -140,6 +140,8 @@ boolean leftmost = ((AquaEditorTabCellRenderer) c).isLeftmost(); boolean closing = pressed && ((AquaEditorTabCellRenderer) c).inCloseButton(); + boolean attention = !pressed && !closing + && ((AquaEditorTabCellRenderer) c).isAttention(); //add in a pixel for rightmost/leftmost so we don't clip off //antialiasing of the curve @@ -161,6 +163,9 @@ } if (closing) { state |= GenericGlowingChiclet.STATE_CLOSING; + } + if (attention) { + state |= GenericGlowingChiclet.STATE_ATTENTION; } chiclet.setArcs(leftarc, rightarc, leftarc, rightarc); Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaSlidingButtonUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaSlidingButtonUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaSlidingButtonUI.java 15 Jun 2004 21:35:42 -0000 1.3 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaSlidingButtonUI.java 10 Sep 2004 18:54:35 -0000 @@ -27,6 +27,7 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicHTML; import javax.swing.text.View; +import org.netbeans.swing.tabcontrol.SlidingButton; import org.netbeans.swing.tabcontrol.SlidingButtonUI; import org.netbeans.swing.tabcontrol.plaf.GenericGlowingChiclet; @@ -73,7 +74,7 @@ protected void paintBackground(Graphics2D graph, AbstractButton b) { chic.setNotch(false, false); - chic.setState(0); + chic.setState(((SlidingButton) b).isBlinkState() ? GenericGlowingChiclet.STATE_ATTENTION : 0); chic.setArcs(0.5f, 0.5f, 0.5f, 0.5f); chic.setBounds(0, 1, b.getWidth() - 2, b.getHeight() - 2); chic.setAllowVertical(true); @@ -87,6 +88,7 @@ state |= b.getModel().isSelected() ? GenericGlowingChiclet.STATE_SELECTED : 0; state |= b.getModel().isPressed() ? GenericGlowingChiclet.STATE_PRESSED : 0; state |= b.getModel().isRollover() ? GenericGlowingChiclet.STATE_ACTIVE : 0; + state |= ((SlidingButton) b).isBlinkState() ? GenericGlowingChiclet.STATE_ATTENTION : 0; chic.setState(state); chic.setArcs(0.5f, 0.5f, 0.5f, 0.5f); chic.setBounds(0, 1, b.getWidth() - 2, b.getHeight() - 2); Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java 1 Sep 2004 15:06:05 -0000 1.9 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java 10 Sep 2004 18:54:35 -0000 @@ -205,6 +205,9 @@ if (isSelected(index)) { state |= GenericGlowingChiclet.STATE_SELECTED; } + if (isAttention(index)) { + state |= GenericGlowingChiclet.STATE_ATTENTION; + } chiclet.setState(state); chiclet.setBounds(x, y, width, height); @@ -299,9 +302,13 @@ Point p = e.getPoint(); int i = getLayoutModel().indexOfPoint(p.x, p.y); int closeRectIdx = inCloseIconRect(p); + tabState.setPressed(i); + tabState.setCloseButtonContainsMouse(closeRectIdx); + tabState.setMousePressedInCloseButton(closeRectIdx); // invoke possible selection change if ((i != -1) && closeRectIdx == -1) { getSelectionModel().setSelectedIndex(i); + tabState.setSelected(i); } if (shouldReact(e) && closeRectIdx != -1) { setClosePressed(closeRectIdx); @@ -317,6 +324,8 @@ } public void mouseReleased(MouseEvent e) { + tabState.setMousePressedInCloseButton(-1); + tabState.setPressed(-1); // close button must not be active when selection change was // triggered by mouse press if (shouldReact(e)) { @@ -338,10 +347,12 @@ public void mouseEntered(MouseEvent me) { setContainsMouse(true); + tabState.setMouseInTabsArea(true); } public void mouseExited(MouseEvent me) { setContainsMouse(false); + tabState.setMouseInTabsArea(false); } } // end of OwnController Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicSlidingTabDisplayerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicSlidingTabDisplayerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicSlidingTabDisplayerUI.java 22 Apr 2004 13:09:11 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicSlidingTabDisplayerUI.java 10 Sep 2004 18:54:36 -0000 @@ -127,6 +127,14 @@ return new MouseAdapter() {}; //XXX } + public void requestAttention (int tab) { + //not implemented + } + + public void cancelRequestAttention (int tab) { + //not implemented + } + protected ChangeListener createSelectionListener() { return new ChangeListener() { private int lastKnownSelection = -1; Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicTabDisplayerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicTabDisplayerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicTabDisplayerUI.java 2 Sep 2004 13:43:09 -0000 1.5 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicTabDisplayerUI.java 10 Sep 2004 18:54:37 -0000 @@ -12,6 +12,7 @@ */ package org.netbeans.swing.tabcontrol.plaf; +import javax.swing.event.ListDataEvent; import org.netbeans.swing.tabcontrol.TabData; import org.netbeans.swing.tabcontrol.TabDisplayer; import org.netbeans.swing.tabcontrol.TabbedContainer; @@ -24,6 +25,7 @@ import java.awt.geom.Area; import java.awt.image.BufferedImage; import java.beans.PropertyChangeListener; +import org.netbeans.swing.tabcontrol.event.ComplexListDataEvent; /** * Base class for tab displayer UIs which use cell renderers to display tabs. @@ -117,6 +119,11 @@ defaultRenderer = null; super.uninstall(); } + + /** Used by unit tests */ + TabState getTabState() { + return tabState; + } /** * Create a TabState instance. TabState manages the state of tabs - that is, which one @@ -218,6 +225,10 @@ if (rect == null) { rect = new Rectangle(); } + if (idx < 0 || idx >= displayer.getModel().size()) { + rect.x = rect.y = rect.width = rect.height = 0; + return rect; + } rect.x = layoutModel.getX(idx); rect.y = layoutModel.getY(idx); rect.width = layoutModel.getW(idx); @@ -449,6 +460,15 @@ protected void processMouseWheelEvent(MouseWheelEvent e) { //do nothing } + + protected final void requestAttention (int tab) { + tabState.addAlarmTab(tab); + } + + protected final void cancelRequestAttention (int tab) { + tabState.removeAlarmTab(tab); + } + protected void modelChanged() { tabState.clearTransientStates(); @@ -457,6 +477,7 @@ //sync int idx = selectionModel.getSelectedIndex(); tabState.setSelected(idx); + tabState.pruneAlarmTabs(displayer.getModel().size()); super.modelChanged(); } @@ -501,16 +522,10 @@ } protected void repaintAllTabs() { - getTabsVisibleArea(scratch); - if (scratch.height < displayer.getHeight()) { - //Ensure any gap at the bottom is repainted - scratch.y = 0; - scratch.height = displayer.getHeight(); - } -/* displayer.repaint(scratch.x, scratch.y, scratch.width, - scratch.height); - */ - //XXX optimize this + //XXX would be nicer to just repaint the tabs area, + //but we also need to repaint below all the tabs in the + //event of activated/deactivated. No actual reason to + //repaint the buttons here. displayer.repaint(); } @@ -530,6 +545,10 @@ scratch.height); } } + + protected ModelListener createModelListener() { + return new BasicModelListener(); + } private class BasicDisplayerPropertyChangeListener extends DisplayerPropertyChangeListener { @@ -722,10 +741,43 @@ assert e.getSource() == selectionModel : "Unknown event source: " + e.getSource(); int idx = selectionModel.getSelectedIndex(); - tabState.setSelected(idx); - if (idx != -1) { + tabState.setSelected(idx >= 0 ? idx : -1); + if (idx >= 0) { makeTabVisible (selectionModel.getSelectedIndex()); } } } + + /** + * Listener on data model which will pass modified indices to the + * TabState object, so it can update which tab indices are flashing in + * "attention" mode, if any. + */ + protected class BasicModelListener extends ModelListener { + public void contentsChanged(ListDataEvent e) { + super.contentsChanged(e); + tabState.contentsChanged(e); + } + + public void indicesAdded(ComplexListDataEvent e) { + super.indicesAdded(e); + tabState.indicesAdded(e); + } + + public void indicesChanged(ComplexListDataEvent e) { + tabState.indicesChanged(e); + } + + public void indicesRemoved(ComplexListDataEvent e) { + tabState.indicesRemoved(e); + } + + public void intervalAdded(ListDataEvent e) { + tabState.intervalAdded(e); + } + + public void intervalRemoved(ListDataEvent e) { + tabState.intervalRemoved(e); + } + } } Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/DefaultTabSelectionModel.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/DefaultTabSelectionModel.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/DefaultTabSelectionModel.java 22 Apr 2004 13:09:13 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/DefaultTabSelectionModel.java 10 Sep 2004 18:54:37 -0000 @@ -88,7 +88,7 @@ } private void adjustSelectionForEvent(ListDataEvent e) { - if (e.getType() == e.CONTENTS_CHANGED) { + if (e.getType() == e.CONTENTS_CHANGED || sel == -1) { return; } int start = e.getIndex0(); @@ -140,6 +140,7 @@ } public void indicesAdded(ComplexListDataEvent e) { + if (sel < 0) return; int[] indices = e.getIndices(); java.util.Arrays.sort(indices); int offset = 0; @@ -157,6 +158,7 @@ } public void indicesRemoved(ComplexListDataEvent e) { + if (sel < 0) return; int[] indices = e.getIndices(); java.util.Arrays.sort(indices); int offset = -1; @@ -176,12 +178,13 @@ sel = -1; fireStateChanged(); } else if (offset != 0) { - sel += offset; + sel = Math.max( -1, Math.min (sel + offset, -1)); fireStateChanged(); } } public void indicesChanged(ComplexListDataEvent e) { + if (sel < 0) return; if (e instanceof VeryComplexListDataEvent) { //it always will be ArrayDiff dif = ((VeryComplexListDataEvent) e).getDiff(); Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/DefaultTabbedContainerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/DefaultTabbedContainerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/DefaultTabbedContainerUI.java 11 Aug 2004 17:09:17 -0000 1.12 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/DefaultTabbedContainerUI.java 10 Sep 2004 18:54:39 -0000 @@ -182,8 +182,11 @@ //#41681, #44278, etc. FOCUS related. the UI needs to be the first listener to be notified // that the selection has changed. Otherwise strange focus effects kick in, eg. when the winsys snapshot gets undated beforehand.. tabDisplayer.getSelectionModel().addChangeListener(selectionListener); - - + } + + /** Used by unit tests */ + TabDisplayer getTabDisplayer() { + return tabDisplayer; } /** This method is final. Subclasses which need to provide additional initialization should override @@ -604,6 +607,14 @@ r.x += p.x; r.y += p.y; return r; + } + + protected void requestAttention (int tab) { + tabDisplayer.requestAttention (tab); + } + + protected void cancelRequestAttention (int tab) { + tabDisplayer.cancelRequestAttention(tab); } /** Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/GenericGlowingChiclet.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/GenericGlowingChiclet.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/GenericGlowingChiclet.java 22 Apr 2004 13:09:13 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/GenericGlowingChiclet.java 10 Sep 2004 18:54:40 -0000 @@ -34,6 +34,7 @@ public static final int STATE_SELECTED = 2; public static final int STATE_ACTIVE = 4; public static final int STATE_CLOSING = 8; + public static final int STATE_ATTENTION = 16; //Basic apple colors. Package access so that GtkChiclet can install its own //defaults if the GTK classes it proxies for colors are not available or @@ -62,6 +63,10 @@ new Color(255, 238, 220), new Color(238, 137, 109), new Color(255, 50, 50), new Color(255, 237, 40)}; + static Color[] attention = new Color[]{ + new Color(255, 255, 220), new Color(238, 237, 109), + new Color(255, 255, 50), new Color(255, 237, 40)}; + private Color upperTop = selectedActive[0]; private Color upperBottom = selectedActive[1]; private Color lowerTop = selectedActive[2]; @@ -124,6 +129,8 @@ Color[] nue; if ((state & STATE_CLOSING) != 0) { nue = closing; + } else if ((state & STATE_ATTENTION) != 0) { + nue = attention; } else { switch (state) { case STATE_PRESSED | STATE_ACTIVE: Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/MetalEditorTabCellRenderer.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/MetalEditorTabCellRenderer.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/MetalEditorTabCellRenderer.java 22 Apr 2004 13:09:15 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/MetalEditorTabCellRenderer.java 10 Sep 2004 18:54:40 -0000 @@ -31,6 +31,7 @@ private static final MetalRightClippedTabPainter rightBorder = new MetalRightClippedTabPainter(); private static final MetalLeftClippedTabPainter leftBorder = new MetalLeftClippedTabPainter(); + static final Color ATTENTION_COLOR = new Color(255, 238, 120); /** * Creates a new instance of MetalEditorTabCellRenderer */ @@ -129,6 +130,10 @@ public void paintInterior(Graphics g, Component c) { MetalEditorTabCellRenderer mtr = (MetalEditorTabCellRenderer) c; + if (mtr.isAttention()) { + g.setColor(ATTENTION_COLOR); + } + Polygon p = getInteriorPolygon(c); g.fillPolygon(p); Rectangle r = new Rectangle(); @@ -246,6 +251,11 @@ public void paintInterior(Graphics g, Component c) { Polygon p = getInteriorPolygon(c); + MetalEditorTabCellRenderer mtr = (MetalEditorTabCellRenderer) c; + if (mtr.isAttention()) { + g.setColor(ATTENTION_COLOR); + } + g.fillPolygon(p); } @@ -340,6 +350,10 @@ public void paintInterior(Graphics g, Component c) { Polygon p = getInteriorPolygon(c); + MetalEditorTabCellRenderer mtr = (MetalEditorTabCellRenderer) c; + if (mtr.isAttention()) { + g.setColor(ATTENTION_COLOR); + } g.fillPolygon(p); } Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/MetalSlidingButtonUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/MetalSlidingButtonUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/MetalSlidingButtonUI.java 2 Sep 2004 13:43:09 -0000 1.3 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/MetalSlidingButtonUI.java 10 Sep 2004 18:54:40 -0000 @@ -101,10 +101,12 @@ } protected void paintBackground (Graphics2D g, AbstractButton button) { + hiddenToggle.setBackground (button.getBackground()); hiddenToggle.paint(g); } protected void paintButtonPressed(Graphics g, AbstractButton b) { + hiddenToggle.setBackground (b.getBackground()); hiddenToggle.paint(g); } Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/MetalViewTabDisplayerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/MetalViewTabDisplayerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/MetalViewTabDisplayerUI.java 1 Sep 2004 15:06:05 -0000 1.9 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/MetalViewTabDisplayerUI.java 10 Sep 2004 18:54:41 -0000 @@ -196,8 +196,12 @@ int width, int height) { boolean selected = isSelected(index); boolean highlighted = selected && isActive(); - if (highlighted) { + boolean attention = isAttention (index); + if (highlighted && !attention) { g.setColor(getActBgColor()); + g.fillRect(x, y, width, height - 3); + } else if (attention) { + g.setColor(MetalEditorTabCellRenderer.ATTENTION_COLOR); g.fillRect(x, y, width, height - 3); } else { g.setColor(getInactBgColor()); Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabState.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabState.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabState.java 9 Sep 2004 00:08:45 -0000 1.3 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabState.java 10 Sep 2004 18:54:42 -0000 @@ -12,6 +12,22 @@ */ package org.netbeans.swing.tabcontrol.plaf; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.swing.Timer; +import javax.swing.event.ListDataEvent; +import org.netbeans.swing.tabcontrol.TabData; +import org.netbeans.swing.tabcontrol.event.ArrayDiff; +import org.netbeans.swing.tabcontrol.event.ComplexListDataEvent; +import org.netbeans.swing.tabcontrol.event.VeryComplexListDataEvent; +import org.openide.util.Utilities; + /** * Used by BasicTabDisplayerUI and its subclasses. * Tracks and manages the state of tabs, mainly which one currently contains the @@ -142,7 +158,18 @@ */ public static final int MOUSE_IN_TABS_AREA = 4096; + /** + * Bitmask indicating that the mouse is inside the close button and has + * been pressed. + */ public static final int MOUSE_PRESSED_IN_CLOSE_BUTTON = 8192; + + /** + * Bitmask indicating that the tab is in "attention" mode - blinking or + * flashing to get the user's attention. + */ + public static final int ATTENTION = 16384; + /** * Indicates the last constant defined - renderers that wish to add their * own bitmasks should use multiples of this number @@ -224,6 +251,9 @@ if (tab == selectedIndex - 1) { result |= BEFORE_SELECTED; } + if (isAlarmTab(tab)) { + result |= ATTENTION; + } return result; } @@ -328,6 +358,7 @@ prev = selectedIndex; selectedIndex = i; curr = i; + removeAlarmTab(i); possibleChange(prev, curr, SELECTED); return prev; } @@ -356,8 +387,304 @@ boolean prev = active; active = b; possibleChange(prev, b, ACTIVE); + removeAlarmTab(selectedIndex); return prev; } + + private boolean isAlarmTab (int tab) { + return attentionToggle && alarmTabs.contains(new Integer(tab)); + } + + private final HashSet alarmTabs = new HashSet(6); + + /** Add a tab to the list of those which should "flash" or otherwise give + * some notification to the user to get their attention */ + public final void addAlarmTab (int alarmTab) { + Integer in = new Integer(alarmTab); + boolean added = alarmTabs.contains(in); + boolean wasEmpty = alarmTabs.isEmpty(); + if (!added) { + alarmTabs.add (new Integer(alarmTab)); + repaintTab (alarmTab); + } + if (wasEmpty) { + startAlarmTimer(); + attentionToggle = true; + repaintTab (alarmTab); + } + } + + /** Remove a tab to the list of those which should "flash" or otherwise give + * some notification to the user to get their attention */ + public final void removeAlarmTab (int alarmTab) { + Integer in = new Integer(alarmTab); + boolean contained = alarmTabs.contains(in); + if (contained) { + alarmTabs.remove(in); + boolean empty = alarmTabs.isEmpty(); + boolean wasAttentionToggled = attentionToggle; + if (alarmTabs.isEmpty()) { + stopAlarmTimer(); + } + if (wasAttentionToggled) { + repaintTab(alarmTab); + } + } + } + + private Timer alarmTimer = null; + private boolean attentionToggle = false; + private final void startAlarmTimer() { + if (alarmTimer == null) { + ActionListener al = new ActionListener() { + public void actionPerformed (ActionEvent ae) { + attentionToggle = !attentionToggle; + Timer timer = (Timer) ae.getSource(); + for (Iterator i=alarmTabs.iterator(); i.hasNext();) { + repaintTab (((Integer) i.next()).intValue()); + } + } + }; + alarmTimer = new Timer (700, al); + alarmTimer.setRepeats(true); + } + alarmTimer.start(); + } + + private final void stopAlarmTimer() { + if (alarmTimer != null && alarmTimer.isRunning()) { + alarmTimer.stop(); + attentionToggle = false; + repaintAllTabs(); //XXX optimize + } + } + + boolean hasAlarmTabs() { + return alarmTabs != null && !alarmTabs.isEmpty(); + } + + void pruneAlarmTabs(int max) { + if (!hasAlarmTabs()) { + return; + } + for (Iterator i=alarmTabs.iterator(); i.hasNext();) { + if (((Integer) i.next()).intValue() >= max) { + i.remove(); + } + } + if (alarmTabs.isEmpty()) { + stopAlarmTimer(); + } + } + + int[] getAlarmTabs() { + int[] alarms = (int[]) Utilities.toPrimitiveArray((Integer[]) alarmTabs.toArray(new Integer[0])); + Arrays.sort(alarms); + return alarms; + } + + //Handling of insertions/deletions where we'll need to update the + //list of blinking tabs here. + void intervalAdded (ListDataEvent evt) { + if (!hasAlarmTabs()) return; + int start = evt.getIndex0(); + int end = evt.getIndex1(); + int[] alarms = (int[]) Utilities.toPrimitiveArray((Integer[]) alarmTabs.toArray(new Integer[0])); + boolean changed = false; + for (int i=0; i < alarms.length; i++) { + if (alarms[i] >= start) { + alarms[i] += (end - start) + 1; + changed = true; + } + } + if (changed) { + alarmTabs.clear(); + for (int i=0; i < alarms.length; i++) { + addAlarmTab(alarms[i]); + } + } + } + + void intervalRemoved (ListDataEvent evt) { + if (!hasAlarmTabs()) return; + int start = evt.getIndex0(); + int end = evt.getIndex1(); + + int[] alarms = (int[]) Utilities.toPrimitiveArray((Integer[]) alarmTabs.toArray(new Integer[0])); + Arrays.sort(alarms); + + if (end == start) { + //Faster to handle this case separately + boolean changed = true; + for (int i=0; i < alarms.length; i++) { + if (alarms[i] > end) { + alarms[i]--; + } else if (alarms[i] == end) { + alarms[i] = -1; + } + } + if (changed) { + alarmTabs.clear(); + boolean added = false; + for (int i=0; i < alarms.length; i++) { + if (alarms[i] != -1) { + addAlarmTab(alarms[i]); + added = true; + } + } + if (!added) { + stopAlarmTimer(); + } + } + return; + } + + boolean changed = false; + for (int i=0; i < alarms.length; i++) { + if (alarms[i] >= start && alarms[i] <= end) { + alarms[i] = -1; + changed = true; + } + } + for (int i=0; i < alarms.length; i++) { + if (alarms[i] > end) { + alarms[i] -= (end - start) + 1; + changed = true; + } + } + if (changed) { + alarmTabs.clear(); + boolean added = false; + for (int i=0; i < alarms.length; i++) { + if (alarms[i] != -1) { + addAlarmTab(alarms[i]); + added = true; + } + } + if (!added) { + stopAlarmTimer(); + } + } + } + + void indicesAdded (ComplexListDataEvent e) { + if (!hasAlarmTabs()) return; + int[] alarms = (int[]) Utilities.toPrimitiveArray((Integer[]) alarmTabs.toArray(new Integer[0])); + java.util.Arrays.sort(alarms); + + int[] indices = e.getIndices(); + java.util.Arrays.sort(indices); + + boolean changed = false; + for (int i=0; i < indices.length; i++) { + for (int j=0; j < alarms.length; j++) { + if (alarms[j] >= indices[i]) { + alarms[j]++; + changed = true; + } + } + } + if (changed) { + alarmTabs.clear(); + for (int i=0; i < alarms.length; i++) { + if (alarms[i] != -1) { + addAlarmTab(alarms[i]); + } + } + } + } + + void indicesRemoved (ComplexListDataEvent e) { + if (!hasAlarmTabs()) return; + int[] indices = e.getIndices(); + java.util.Arrays.sort(indices); + + int[] alarms = (int[]) Utilities.toPrimitiveArray((Integer[]) alarmTabs.toArray(new Integer[0])); + java.util.Arrays.sort(alarms); + + if (alarms[alarms.length -1] < indices[0]) { + //Some tab removed after the last blinking tab, don't care + return; + } + + boolean changed = false; + for (int i=0; i < alarms.length; i++) { + //First weed out all deleted alarm tabs + for (int j=0; j < indices.length; j++) { + if (alarms[i] == indices[j]) { + alarms[i] = -1; + changed = true; + } + } + } + for (int i=0; i < alarms.length; i++) { + //Now decrement those that remain that are affected + int alarm = alarms[i]; + for (int j=0; j < indices.length; j++) { + if (alarm > indices[j]) { + alarms[i]--; + changed = true; + } + } + } + + if (changed) { + alarmTabs.clear(); + boolean addedSome = false; + for (int i=0; i < alarms.length; i++) { + if (alarms[i] >= 0) { + addAlarmTab(alarms[i]); + addedSome = true; + } + } + if (!addedSome) { + stopAlarmTimer(); + } + } + + repaintAllTabs(); + } + + void indicesChanged (ComplexListDataEvent e) { + if (!hasAlarmTabs()) return; + if (e instanceof VeryComplexListDataEvent) { //it always will be + VeryComplexListDataEvent ve = (VeryComplexListDataEvent) e; + + ArrayDiff dif = ((VeryComplexListDataEvent) e).getDiff(); + + List old = Arrays.asList(dif.getOldData()); + List nue = Arrays.asList(dif.getNewData()); + + int[] alarms = (int[]) Utilities.toPrimitiveArray((Integer[]) alarmTabs.toArray(new Integer[0])); + + boolean changed = false; + for (int i=0; i < alarms.length; i++) { + Object o = old.get(alarms[i]); + int idx = nue.indexOf(o); + changed |= idx != alarms[i]; + alarms[i] = nue.indexOf(o); + } + if (changed) { + alarmTabs.clear(); + boolean addedSome = false; + for (int i=0; i < alarms.length; i++) { + if (alarms[i] >= 0) { + addAlarmTab(alarms[i]); + addedSome = true; + } + } + if (!addedSome) { + stopAlarmTimer(); + } + } + } + } + + + void contentsChanged(ListDataEvent evt) { + if (!hasAlarmTabs()) return; + //Do nothing, just means some text or icons changed + } //Change types /** Change type indicating no change happened (i.e. calling setSelected() with the same value it was previously @@ -508,6 +835,9 @@ type = ALL_TABS; go = true; } + break; + case ATTENTION: + go = true; } if (go) { if (type == ALL_TABS) { Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/ToolbarTabDisplayerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/ToolbarTabDisplayerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/ToolbarTabDisplayerUI.java 15 Jun 2004 00:37:46 -0000 1.1 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/ToolbarTabDisplayerUI.java 10 Sep 2004 18:54:42 -0000 @@ -102,7 +102,15 @@ } } return null; - } + } + + public void requestAttention (int tab) { + //not implemented + } + + public void cancelRequestAttention (int tab) { + //not implemented + } protected ChangeListener createSelectionListener() { return new ChangeListener() { Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinClassicEditorTabCellRenderer.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinClassicEditorTabCellRenderer.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinClassicEditorTabCellRenderer.java 5 May 2004 18:50:27 -0000 1.3 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinClassicEditorTabCellRenderer.java 10 Sep 2004 18:54:43 -0000 @@ -31,6 +31,8 @@ private static final TabPainter rightClip = new WinClassicRightClipPainter(); private static final TabPainter normal = new WinClassicPainter(); + static final Color ATTENTION_COLOR = new Color(255, 238, 120); + private static boolean isGenericUI = !"Windows".equals( UIManager.getLookAndFeel().getID()); @@ -135,9 +137,13 @@ ((Graphics2D) g).setPaint(ColorUtil.getGradientPaint(0, 0, getSelGradientColor(), ren.getWidth(), 0, UIManager.getColor( "TabbedPane.background")));//NOI18N } else { - g.setColor(ren.isSelected() ? - UIManager.getColor("TabbedPane.background") : - UIManager.getColor("tab_unsel_fill")); //NOI18N + if (!ren.isAttention()) { + g.setColor(ren.isSelected() ? + UIManager.getColor("TabbedPane.background") : + UIManager.getColor("tab_unsel_fill")); //NOI18N + } else { + g.setColor(ATTENTION_COLOR); + } } Polygon p = getInteriorPolygon(c); g.fillPolygon(p); @@ -260,9 +266,13 @@ ((Graphics2D) g).setPaint(ColorUtil.getGradientPaint(0, 0, getSelGradientColor(), ren.getWidth(), 0, UIManager.getColor( "TabbedPane.background")));//NOI18N } else { - g.setColor(ren.isSelected() ? + if (!ren.isAttention()) { + g.setColor(ren.isSelected() ? UIManager.getColor("TabbedPane.background") : UIManager.getColor("tab_unsel_fill")); //NOI18N + } else { + g.setColor(ATTENTION_COLOR); + } } Polygon p = getInteriorPolygon(c); g.fillPolygon(p); @@ -353,9 +363,13 @@ ((Graphics2D) g).setPaint(ColorUtil.getGradientPaint(0, 0, getSelGradientColor(), ren.getWidth(), 0, UIManager.getColor( "TabbedPane.background")));//NOI18N } else { - g.setColor(ren.isSelected() ? - UIManager.getColor("TabbedPane.background") : + if (!ren.isAttention()) { + g.setColor(ren.isSelected() ? + UIManager.getColor("TabbedPane.background") : //NOI18N UIManager.getColor("tab_unsel_fill")); //NOI18N + } else { + g.setColor(ATTENTION_COLOR); + } } Polygon p = getInteriorPolygon(c); Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinClassicViewTabDisplayerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinClassicViewTabDisplayerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinClassicViewTabDisplayerUI.java 2 Sep 2004 13:43:09 -0000 1.10 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinClassicViewTabDisplayerUI.java 10 Sep 2004 18:54:43 -0000 @@ -228,16 +228,20 @@ // background body, colored according to state boolean selected = isSelected(index); boolean focused = isFocused(index); + boolean attention = isAttention(index); + Paint result = null; - if (focused) { + if (focused && !attention) { result = ColorUtil.getGradientPaint(x, y, getSelGradientColor(), //NOI18N x + width, y, UIManager.getColor( "TabbedPane.background")); - } else if (selected) { + } else if (selected && !attention) { result = UIManager.getColor("TabbedPane.background"); //NOI18N + } else if (attention) { + result = WinClassicEditorTabCellRenderer.ATTENTION_COLOR; } else { result = UIManager.getColor("tab_unsel_fill"); } Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinXPEditorTabCellRenderer.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinXPEditorTabCellRenderer.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinXPEditorTabCellRenderer.java 22 Apr 2004 13:09:18 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinXPEditorTabCellRenderer.java 10 Sep 2004 18:54:44 -0000 @@ -252,7 +252,13 @@ private static final Paint getPaint(int xTop, int yTop, int xBot, int yBot, WinXPEditorTabCellRenderer ren) { - if (ren.isSelected() || (ren.isPressed() && !ren.inCloseButton())) { + + if (!ren.isSelected() && !ren.isPressed() && ren.isAttention()) { + Color a = new Color (255, 255, 128); + Color b = new Color (230, 200, 64); + return ColorUtil.getGradientPaint(xTop, yTop, a, xBot, + yBot, b); + } else if (ren.isSelected() || (ren.isPressed() && !ren.inCloseButton())) { if (ren.isActive()) { return ColorUtil.getGradientPaint(xTop, yTop, getTopActiveSelectedColor(), Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinXPSlidingButtonUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinXPSlidingButtonUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinXPSlidingButtonUI.java 2 Sep 2004 13:43:10 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinXPSlidingButtonUI.java 10 Sep 2004 18:54:44 -0000 @@ -39,6 +39,7 @@ import javax.swing.border.CompoundBorder; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicGraphicsUtils; +import org.netbeans.swing.tabcontrol.SlidingButton; import org.netbeans.swing.tabcontrol.SlidingButtonUI; /** @@ -108,12 +109,17 @@ super.paint(g, c); } - protected void paintBackground (Graphics2D g, AbstractButton button) { - hiddenToggle.paint(g); + protected void paintBackground(Graphics2D g, AbstractButton b) { + if (((SlidingButton) b).isBlinkState()) { + g.setColor(WinClassicEditorTabCellRenderer.ATTENTION_COLOR); + g.fillRect (0, 0, b.getWidth(), b.getHeight()); + hiddenToggle.setFont(b.getFont()); + } else { + hiddenToggle.paint(g); + } } protected void paintButtonPressed(Graphics g, AbstractButton b) { hiddenToggle.paint(g); } - } Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinXPViewTabDisplayerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinXPViewTabDisplayerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinXPViewTabDisplayerUI.java 1 Sep 2004 15:06:05 -0000 1.8 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinXPViewTabDisplayerUI.java 10 Sep 2004 18:54:45 -0000 @@ -190,12 +190,18 @@ // background body, colored according to state boolean selected = isSelected(index); boolean focused = selected && isActive(); - if (focused) { + boolean attention = isAttention(index); + if (focused && !attention) { ColorUtil.xpFillRectGradient((Graphics2D) g, x, y, width, height, focusFillBrightC, focusFillDarkC); - } else if (selected && isMoreThanOne()) { + } else if (selected && isMoreThanOne() && !attention) { g.setColor(selFillC); g.fillRect(x, y, width, height); + } else if (attention) { + Color a = new Color (255, 255, 128); + Color b = new Color (230, 200, 64); + ColorUtil.xpFillRectGradient((Graphics2D) g, x, y, width, height, + a, b); } else { ColorUtil.xpFillRectGradient((Graphics2D) g, x, y, width, height, unselFillBrightC, unselFillDarkC); @@ -465,11 +471,13 @@ public Icon getIcon() { - if (displayer.isActive()) { - return iconCache.obtainIcon((String)focusedNormal.get(getOrientation())); + Icon result = null; + if (displayer != null && displayer.isActive()) { + result = iconCache.obtainIcon((String)focusedNormal.get(getOrientation())); } else { - return super.getIcon(); + result = super.getIcon(); } + return result; } public Icon getRolloverIcon() { Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WindowsSlidingButtonUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WindowsSlidingButtonUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WindowsSlidingButtonUI.java 2 Jun 2004 12:31:31 -0000 1.1 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WindowsSlidingButtonUI.java 10 Sep 2004 18:54:45 -0000 @@ -37,6 +37,7 @@ import javax.swing.border.CompoundBorder; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicGraphicsUtils; +import org.netbeans.swing.tabcontrol.SlidingButton; import org.netbeans.swing.tabcontrol.SlidingButtonUI; /** @@ -77,10 +78,15 @@ public void installDefaults (AbstractButton b) { super.installDefaults(b); if(!defaults_initialized) { - dashedRectGapX = ((Integer)UIManager.get("Button.dashedRectGapX")).intValue(); - dashedRectGapY = ((Integer)UIManager.get("Button.dashedRectGapY")).intValue(); - dashedRectGapWidth = ((Integer)UIManager.get("Button.dashedRectGapWidth")).intValue(); - dashedRectGapHeight = ((Integer)UIManager.get("Button.dashedRectGapHeight")).intValue(); + //Null checks so this can be tested on other platforms + Integer in = ((Integer)UIManager.get("Button.dashedRectGapX")); + dashedRectGapX = in == null ? 3 : in.intValue(); + in = ((Integer)UIManager.get("Button.dashedRectGapY")); + dashedRectGapY = in == null ? 3 : in.intValue(); + in = ((Integer)UIManager.get("Button.dashedRectGapWidth")); + dashedRectGapWidth = in == null ? 3 : in.intValue(); + in = ((Integer)UIManager.get("Button.dashedRectGapHeight")); + dashedRectGapHeight = in == null ? 3 : in.intValue(); focusColor = UIManager.getColor(getPropertyPrefix() + "focus"); defaults_initialized = true; @@ -92,15 +98,24 @@ defaults_initialized = false; } - protected void paintBackground (Graphics2D g, AbstractButton button) { - super.paintBackground(g, button); - } - + protected void paintBackground(Graphics2D g, AbstractButton b) { + if (((SlidingButton) b).isBlinkState()) { + g.setColor(WinClassicEditorTabCellRenderer.ATTENTION_COLOR); + g.fillRect (0, 0, b.getWidth(), b.getHeight()); + } else { + super.paintBackground(g, b); + } + } protected void paintButtonPressed(Graphics g, AbstractButton b) { // This is a special case in which the toggle button in the // Rollover JToolBar will render the button in a pressed state Color oldColor = g.getColor(); + + if (((SlidingButton) b).isBlinkState()) { + g.setColor(WinClassicEditorTabCellRenderer.ATTENTION_COLOR); + g.fillRect (0, 0, b.getWidth(), b.getHeight()); + } int w = b.getWidth(); int h = b.getHeight(); Index: core/swing/tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/DataModelTest.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/DataModelTest.java,v --- core/swing/tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/DataModelTest.java 22 Apr 2004 13:09:27 -0000 1.2 +++ core/swing/tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/DataModelTest.java 10 Sep 2004 18:54:46 -0000 @@ -200,8 +200,8 @@ public void doTestRemoveContiguous() { System.err.println("testRemoveContiguous"); int formerSize = mdl.size(); - mdl.removeTabs (10, 20); - int expectedSize=formerSize-10; + mdl.removeTabs (10, 19); + int expectedSize=formerSize - 10; assertPravda (mdl.size() == expectedSize, "Model size should be " + expectedSize + " after removing 10 items, but is " + mdl.size()); try { _testContentsValid(); Index: core/swing/tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/AttentionAndModelChangesTest.java =================================================================== RCS file: core/swing/tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/AttentionAndModelChangesTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/swing/tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/AttentionAndModelChangesTest.java 10 Sep 2004 18:54:46 -0000 @@ -0,0 +1,554 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.swing.tabcontrol.plaf; + +import java.awt.BorderLayout; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.SwingUtilities; +import junit.framework.*; +import org.netbeans.swing.tabcontrol.DefaultTabDataModel; +import org.netbeans.swing.tabcontrol.TabData; +import org.netbeans.swing.tabcontrol.TabDataModel; +import org.netbeans.swing.tabcontrol.TabDisplayer; +import org.netbeans.swing.tabcontrol.TabbedContainer; + +/** + * TabState stores a list of tabs which are blinking for attention. + * They are stored by index. This test ensures that when tabs are added + * or removed from the model, that the indices are updated correctly. + *

+ * This test also exercises the data model heavily. + * + * @author Tim Boudreau + */ +public class AttentionAndModelChangesTest extends TestCase { + + public AttentionAndModelChangesTest(java.lang.String testName) { + super(testName); + } + + public static Test suite() { + TestSuite suite = new TestSuite(AttentionAndModelChangesTest.class); + return suite; + } + + TabDataModel mdl = null; + TabData[] origData = null; + TabState state = null; + TabDisplayer displayer = null; + TabbedContainer tab = null; + public void setUp() { + origData = new TabData[13]; + for (int i=0; i < origData.length; i++) { + origData[i] = new TabData(new JLabel("TD " + i), null, "TD " + i, "tip"); + } + + mdl = new DefaultTabDataModel(origData); + + tab = new TabbedContainer(mdl, TabbedContainer.TYPE_EDITOR); + tab.setActive(true); + JFrame jf = new JFrame(); + jf.getContentPane().setLayout (new BorderLayout()); + jf.getContentPane().add (tab, BorderLayout.CENTER); + jf.setBounds (20, 20, 400, 200); + jf.show(); + jf.toFront(); + while (!jf.isShowing()) { + try { + Thread.currentThread().sleep(50); + } catch (InterruptedException e) { + fail (e.toString()); + } + } + displayer = ((DefaultTabbedContainerUI) tab.getUI()).getTabDisplayer(); + BasicTabDisplayerUI ui = (BasicTabDisplayerUI) displayer.getUI(); + state = ui.getTabState(); + } + + private void requestAttention (final int i) throws Exception { + SwingUtilities.invokeAndWait (new Runnable() { + public void run() { + tab.requestAttention(i); + } + }); + } + + private void assertAtt (final int[] i) throws Exception { + Arrays.sort (i); + int[] att = state.getAlarmTabs(); + assertTrue ("Expected attention tabs to be " + + a2s(i) + " but they are " + a2s(att), + Arrays.equals(i, att)); + } + + private void assertNotAtt (final int[] i) throws Exception { + SwingUtilities.invokeAndWait (new Runnable() { + public void run() { + Arrays.sort (i); + int[] att = state.getAlarmTabs(); + assertFalse ("Expected attention tabs NOT to be " + + a2s(i) + " but they are " + a2s(att), + Arrays.equals(i, att)); + } + }); + } + + private static String a2s (int[] ints) { + if (ints == null) { + return "null"; + } + if (ints.length == 0) { + return "[]"; + } + StringBuffer sb = new StringBuffer(); + for (int i=0; i < ints.length; i++) { + sb.append (ints[i]); + if (i != ints.length -1) { + sb.append(','); + } + } + return sb.toString(); + } + + public void testRequestAttention () throws Exception { + System.out.println("testRequestAttention"); + requestAttention (3); + requestAttention (5); + assertAtt(new int[] {3, 5}); + } + + public void testRemoveSingleInBetween() throws Exception { + System.out.println("testRemoveSingleInBetween"); + requestAttention(3); + requestAttention(5); + mdl.removeTab(4); + assertAtt(new int[] {3, 4}); + } + + public void testRemoveSingleBefore() throws Exception { + System.out.println("testRemoveSingleBefore"); + requestAttention(3); + requestAttention(5); + mdl.removeTab(2); + assertAtt(new int[] {2, 4}); + } + + + public void testRemoveSingleAfter() throws Exception { + System.out.println("testRemoveSingleAfter"); + requestAttention(3); + requestAttention(5); + mdl.removeTab(6); + assertAtt(new int[] {3, 5}); + mdl.removeTab(10); + assertAtt(new int[] {3, 5}); + } + + public void testRemoveSingleFirst() throws Exception { + System.out.println("testRemoveSingleFirst"); + requestAttention(3); + requestAttention(5); + mdl.removeTab(3); + assertAtt(new int[] {4}); + } + + + public void testRemoveSingleSecond() throws Exception { + System.out.println("testRemoveSingleSecond"); + requestAttention(3); + requestAttention(5); + mdl.removeTab(5); + assertAtt(new int[] {3}); + } + + public void testAddSingleBefore() throws Exception { + System.out.println("testAddSingleBefore"); + requestAttention(3); + requestAttention(5); + TabData td = new TabData (new JLabel("Added"), null, "Added", "tip"); + mdl.addTab(1, td); + assertAtt(new int[] {4, 6}); + } + + public void testAddSingleBetween() throws Exception { + System.out.println("testAddSingleBetween"); + requestAttention(3); + requestAttention(5); + TabData td = new TabData (new JLabel("Added"), null, "Added", "tip"); + mdl.addTab(4, td); + assertAtt(new int[] {3, 6}); + } + + public void testAddSingleAt() throws Exception { + System.out.println("testAddSingleAt"); + requestAttention(3); + requestAttention(5); + TabData td = new TabData (new JLabel("Added"), null, "Added", "tip"); + mdl.addTab(3, td); + assertAtt(new int[] {4, 6}); + } + + public void testAddSingleAfter() throws Exception { + System.out.println("testAddSingleAfter"); + requestAttention(3); + requestAttention(5); + TabData td = new TabData (new JLabel("Added"), null, "Added", "tip"); + mdl.addTab(7, td); + assertAtt(new int[] {3, 5}); + } + + public void testAddSingleAtEnd() throws Exception { + System.out.println("testAddSingleAtEnd"); + requestAttention(3); + requestAttention(5); + TabData td = new TabData (new JLabel("Added"), null, "Added", "tip"); + mdl.addTab(5, td); + assertAtt(new int[] {3, 6}); + } + + public void testAddMultipleBefore() throws Exception { + System.out.println("testAddMultipleBefore"); + requestAttention(3); + requestAttention(5); + TabData att1 = mdl.getTab(3); + TabData att2 = mdl.getTab(5); + + TabData td1 = new TabData (new JLabel("Added 1"), null, "Added 1", "tip"); + TabData td2 = new TabData (new JLabel("Added 2"), null, "Added 2", "tip"); + mdl.addTabs(1, new TabData[] {td1, td2}); + assertAtt(new int[] {5, 7}); + + int[] tabs = state.getAlarmTabs(); + assertSame ("First attention tab should be " + att1 + " not " + mdl.getTab(tabs[0]), att1, mdl.getTab(tabs[0])); + assertSame ("Second attention tab should be " + att2 + " not " + mdl.getTab(tabs[1]), att2, mdl.getTab(tabs[1])); + } + + + public void testAddMultipleAfter() throws Exception { + System.out.println("testAddMultipleAfter"); + requestAttention(3); + requestAttention(5); + TabData td1 = new TabData (new JLabel("Added 1"), null, "Added 1", "tip"); + TabData td2 = new TabData (new JLabel("Added 2"), null, "Added 2", "tip"); + mdl.addTabs(7, new TabData[] {td1, td2}); + assertAtt(new int[] {3, 5}); + } + + public void testAddMultipleBetween() throws Exception { + System.out.println("testAddMultipleBetween"); + requestAttention(3); + requestAttention(5); + TabData att1 = mdl.getTab(3); + TabData att2 = mdl.getTab(5); + + TabData td1 = new TabData (new JLabel("Added 1"), null, "Added 1", "tip"); + TabData td2 = new TabData (new JLabel("Added 2"), null, "Added 2", "tip"); + mdl.addTabs(4, new TabData[] {td1, td2}); + assertAtt(new int[] {3, 7}); + + int[] tabs = state.getAlarmTabs(); + assertSame ("First attention tab should be " + att1 + " not " + mdl.getTab(tabs[0]), att1, mdl.getTab(tabs[0])); + assertSame ("Second attention tab should be " + att2 + " not " + mdl.getTab(tabs[1]), att2, mdl.getTab(tabs[1])); + } + + + public void testRemoveMultipleBetween() throws Exception { + System.out.println("testRemoveMultipleBetween"); + requestAttention(3); + requestAttention(6); + TabData att1 = mdl.getTab(3); + TabData att2 = mdl.getTab(6); + + int sz = mdl.size(); + mdl.removeTabs(4, 5); + assertTrue ("After removing tabs 4 and 5, model size should be 2 smaller - was " + sz + " is " + mdl.size(), mdl.size() == sz-2); + + assertAtt (new int[] {3, 4}); + + int[] tabs = state.getAlarmTabs(); + System.out.println("Alarm tabs are " + tabs[0] + "," + tabs[1]); + System.err.println("First attention tab " + mdl.getTab(tabs[0])); + System.err.println("Second attention tab " + mdl.getTab(tabs[1])); + assertSame ("First attention tab should be " + att1 + " not " + mdl.getTab(tabs[0]), att1, mdl.getTab(tabs[0])); + assertSame ("Second attention tab should be " + att2 + " not " + mdl.getTab(tabs[1]), att2, mdl.getTab(tabs[1])); + } + + public void testRemoveMultipleBefore() throws Exception { + System.out.println("testRemoveMultipleBefore"); + requestAttention(3); + requestAttention(6); + TabData att1 = mdl.getTab(3); + TabData att2 = mdl.getTab(6); + int sz = mdl.size(); + + mdl.removeTabs(1, 2); + + assertTrue ("After removing tabs 1 and 2, model size should be 2 smaller - was " + sz + " is " + mdl.size(), mdl.size() == sz-2); + assertAtt (new int[] {1, 4}); + + int[] tabs = state.getAlarmTabs(); + assertSame ("First attention tab should be " + att1 + " not " + mdl.getTab(tabs[0]), att1, mdl.getTab(tabs[0])); + assertSame ("Second attention tab should be " + att2 + " not " + mdl.getTab(tabs[1]), att2, mdl.getTab(tabs[1])); + } + + public void testRemoveMultipleAfter() throws Exception { + System.out.println("testRemoveMultipleAfter"); + requestAttention(3); + requestAttention(6); + mdl.removeTabs(7, 10); + assertAtt (new int[] {3, 6}); + } + + public void testRemoveMultipleIncluding() throws Exception { + System.out.println("testRemoveMultipleIncluding"); + requestAttention(3); + requestAttention(6); + mdl.removeTabs(6, 10); + assertAtt (new int[] {3}); + } + + public void testRemoveMultipleInclusive() throws Exception { + System.out.println("testRemoveMultipleInclusive"); + requestAttention(3); + requestAttention(6); + mdl.removeTabs(2, 7); + assertAtt (new int[0]); + } + + public void testAddDiscontiguousBefore() throws Exception { + System.out.println("testAddDiscontiguousBefore"); + requestAttention(7); + requestAttention(9); + TabData td1 = new TabData (new JLabel("Added 1"), null, "Added 1", "tip"); + TabData td2 = new TabData (new JLabel("Added 2"), null, "Added 2", "tip"); + TabData td3 = new TabData (new JLabel("Added 3"), null, "Added 3", "tip"); + mdl.addTabs (new int[] { 1, 3, 5}, new TabData[] {td1, td2, td3}); + assertAtt (new int[] {10, 12}); + } + + public void testRemoveDiscontiguousBefore() throws Exception { + System.out.println("testRemoveDiscontiguousBefore"); + requestAttention(7); + requestAttention(9); + mdl.removeTabs (new int[] {1,3,5}); + assertAtt (new int[] {4, 6}); + } + + public void testRemoveDiscontiguousAfter() throws Exception { + System.out.println("testRemoveDiscontiguousAfter"); + requestAttention(5); + requestAttention(7); + mdl.removeTabs (new int[] {8,10,11}); + assertAtt (new int[] {5, 7}); + } + + + public void testAddDiscontiguousBeforeAndBetween() throws Exception { + System.out.println("testAddDiscontiguousBeforeAndBetween"); + requestAttention(7); + requestAttention(9); + TabData att1 = mdl.getTab(7); + TabData att2 = mdl.getTab(9); + + TabData td1 = new TabData (new JLabel("Added 1"), null, "Added 1", "tip"); + TabData td2 = new TabData (new JLabel("Added 2"), null, "Added 2", "tip"); + TabData td3 = new TabData (new JLabel("Added 3"), null, "Added 3", "tip"); + mdl.addTabs (new int[] {1, 3, 8}, new TabData[] {td1, td2, td3}); + assertAtt (new int[] {10, 12}); + + int[] tabs = state.getAlarmTabs(); + assertSame ("First attention tab should be " + att1 + " not " + mdl.getTab(tabs[0]), att1, mdl.getTab(tabs[0])); + assertSame ("Second attention tab should be " + att2 + " not " + mdl.getTab(tabs[1]), att2, mdl.getTab(tabs[1])); + } + + public void testRemoveDiscontiguousBeforeAndBetween() throws Exception { + System.out.println("testRemoveDiscontiguousBeforeAndBetween"); + requestAttention(7); + requestAttention(9); + TabData att1 = mdl.getTab(7); + TabData att2 = mdl.getTab(9); + mdl.removeTabs (new int[] {1,3,8}); + assertAtt (new int[] {5, 6}); + int[] tabs = state.getAlarmTabs(); + assertSame ("First attention tab should be " + att1 + " not " + mdl.getTab(tabs[0]), att1, mdl.getTab(tabs[0])); + assertSame ("Second attention tab should be " + att2 + " not " + mdl.getTab(tabs[1]), att2, mdl.getTab(tabs[1])); + } + + public void testRemoveDiscontiguousBeforeAndBetweenIncludingOne() throws Exception { + System.out.println("testRemoveDiscontiguousBeforeAndBetweenIncludingOne"); + requestAttention(7); + requestAttention(9); + TabData att1 = mdl.getTab(7); + TabData att2 = mdl.getTab(9); + mdl.removeTabs (new int[] {1,3,7}); + assertAtt (new int[] {6}); + } + + public void testReorderComplex() throws Exception { + System.out.println("testReorderComplex"); + requestAttention(7); + requestAttention(9); + TabData att1 = mdl.getTab(7); + TabData att2 = mdl.getTab(9); + + TabData[] data = (TabData[]) mdl.getTabs().toArray(new TabData[mdl.size()]); + TabData[] nue = new TabData[data.length]; + for (int i=data.length-1; i >=0; i--) { + nue [data.length-(i+1)] = data[i]; + } + mdl.setTabs(nue); + int[] tabs = state.getAlarmTabs(); + Arrays.sort(tabs); + assertSame ("First attention tab should be " + att2 + " not " + mdl.getTab(tabs[0]), att2, mdl.getTab(tabs[0])); + assertSame ("Second attention tab should be " + att1 + " not " + mdl.getTab(tabs[1]), att1, mdl.getTab(tabs[1])); + } + + public void testReplaceComplex() throws Exception { + System.out.println("testReplaceComplex"); + requestAttention(7); + requestAttention(9); + + TabData[] data = (TabData[]) mdl.getTabs().toArray(new TabData[mdl.size()]); + data[7] = new TabData (new JLabel("foo"), null, "foo", "foo"); + mdl.setTabs(data); + + assertAtt(new int[] {9}); + + data[9] = new TabData (new JLabel("goo"), null, "goo", "goo"); + mdl.setTabs(data); + assertAtt(new int[0]); + } + + public void testRemoveComplex() throws Exception { + System.out.println("testRemoveComplex"); + requestAttention(7); + requestAttention(9); + TabData att1 = mdl.getTab(7); + TabData att2 = mdl.getTab(9); + + TabData[] data = (TabData[]) mdl.getTabs().toArray(new TabData[mdl.size()]); + TabData[] nue = new TabData[data.length-1]; + int ct = 0; + for (int i=0; i < data.length; i++) { + nue [ct] = data[i]; + if (i != 7) { + ct++; + } + } + mdl.setTabs(nue); + int[] tabs = state.getAlarmTabs(); + + assertTrue (tabs.length ==1); + assertTrue (tabs[0] == 8); + assertSame (mdl.getTab(tabs[0]), att2); + } + + + public void testAddComplex() throws Exception { + System.out.println("testAddComplex"); + requestAttention(7); + requestAttention(9); + TabData att1 = mdl.getTab(7); + TabData att2 = mdl.getTab(9); + + TabData[] data = (TabData[]) mdl.getTabs().toArray(new TabData[mdl.size()]); + + ArrayList al = new ArrayList (Arrays.asList(data)); + al.add (8, new TabData(new JLabel("boo"), null, "boo", "boo")); + al.add (2, new TabData(new JLabel("moo"), null, "moo", "moo")); + + TabData[] nue = (TabData[]) al.toArray(new TabData[0]); + + mdl.setTabs(nue); + int[] tabs = state.getAlarmTabs(); + Arrays.sort(tabs); + assertSame ("First attention tab should be " + att1 + " not " + mdl.getTab(tabs[0]), att1, mdl.getTab(tabs[0])); + assertSame ("Second attention tab should be " + att2 + " not " + mdl.getTab(tabs[1]), att2, mdl.getTab(tabs[1])); + } + + //Make sure (non)exceptional case of only 1 tab works + + private void makeSingle() throws Exception { + SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + TabData[] td = new TabData[] { + new TabData (new JLabel("Single"), null, "single", "single") + }; + mdl.setTabs(td); + state.addAlarmTab(0); + assertTrue (state.getAlarmTabs().length == 1); + } + }); + Thread.currentThread().sleep(50); + } + + public void testHandleSingle1() throws Exception { + System.out.println("testHandleSingle1"); + makeSingle(); + + mdl.removeTab(0); + assertAtt (new int[0]); + } + + public void testHandleSingle2() throws Exception { + System.out.println("testHandleSingle2"); + makeSingle(); + mdl.removeTabs(0, 0); + assertAtt (new int[0]); + } + + public void testHandleSingle3() throws Exception { + System.out.println("testHandleSingle3"); + makeSingle(); + mdl.removeTabs(new int[] {0}); + assertAtt (new int[0]); + } + + public void testAddSingle1() throws Exception { + System.out.println("testAddSingle1"); + makeSingle(); + mdl.addTab(0, new TabData(new JLabel("foo"), null, "foo", "foo")); + assertTrue ("Size should be 2", mdl.size() == 2); + assertAtt(new int[] {1}); + } + + public void testAddSingle2() throws Exception { + System.out.println("testAddSingle2"); + makeSingle(); + mdl.addTabs(0, new TabData[] {new TabData(new JLabel("foo"), null, "foo", "foo")}); + assertTrue ("Size should be 2", mdl.size() == 2); + assertAtt(new int[] {1}); + } + + public void testAddSingle3() throws Exception { + System.out.println("testAddSingle3"); + makeSingle(); + mdl.addTabs(new int[] {0}, new TabData[] {new TabData(new JLabel("foo"), null, "foo", "foo")}); + assertTrue ("Size should be 2", mdl.size() == 2); + assertAtt(new int[] {1}); + } + + public void testAddSingle4() throws Exception { + System.out.println("testAddSingle4"); + makeSingle(); + mdl.addTabs(new int[] {1}, new TabData[] {new TabData(new JLabel("foo"), null, "foo", "foo")}); + assertTrue ("Size should be 2", mdl.size() == 2); + assertAtt(new int[] {0}); + } +} Index: core/swing/tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/TabStateTest.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/TabStateTest.java,v --- core/swing/tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/TabStateTest.java 22 Apr 2004 13:09:28 -0000 1.2 +++ core/swing/tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/TabStateTest.java 10 Sep 2004 18:54:47 -0000 @@ -323,11 +323,4 @@ } private int policy = 0; - - - // TODO add test methods here, they have to start with 'test' name. - // for example: - // public void testHello() {} - - } Index: core/windows/src/org/netbeans/core/windows/Central.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/Central.java,v --- core/windows/src/org/netbeans/core/windows/Central.java 3 Sep 2004 09:55:43 -0000 1.25 +++ core/windows/src/org/netbeans/core/windows/Central.java 10 Sep 2004 18:54:50 -0000 @@ -48,6 +48,18 @@ } + public void topComponentRequestAttention (ModeImpl mode, TopComponent tc) { + String modeName = getModeName(mode); + viewRequestor.scheduleRequest ( + new ViewRequest(modeName, View.TOPCOMPONENT_REQUEST_ATTENTION, tc, tc)); + } + + public void topComponentCancelRequestAttention (ModeImpl mode, TopComponent tc) { + String modeName = getModeName(mode); + viewRequestor.scheduleRequest ( + new ViewRequest(modeName, View.TOPCOMPONENT_CANCEL_REQUEST_ATTENTION, tc, tc)); + } + ///////////////////// // Mutators >> /** Sets visible or invisible window system and requests view accordingly. */ Index: core/windows/src/org/netbeans/core/windows/ViewRequest.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/ViewRequest.java,v --- core/windows/src/org/netbeans/core/windows/ViewRequest.java 28 May 2004 10:59:42 -0000 1.3 +++ core/windows/src/org/netbeans/core/windows/ViewRequest.java 10 Sep 2004 18:54:50 -0000 @@ -118,6 +118,12 @@ case View.CHANGE_PROJECT_NAME : tp = "CHANGE_PROJECT_NAME"; //NOI18N break; + case View.TOPCOMPONENT_REQUEST_ATTENTION : + tp = "TOPCOMPONENT_REQUEST_ATTENTION"; //NOI18N + break; + case View.TOPCOMPONENT_CANCEL_REQUEST_ATTENTION : + tp = "TOPCOMPONENT_CANCEL_REQUEST_ATTENTION"; //NOI18N + break; default : tp = "UNKNOWN"; break; Index: core/windows/src/org/netbeans/core/windows/ViewRequestor.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/ViewRequestor.java,v --- core/windows/src/org/netbeans/core/windows/ViewRequestor.java 28 May 2004 10:59:42 -0000 1.10 +++ core/windows/src/org/netbeans/core/windows/ViewRequestor.java 10 Sep 2004 18:54:51 -0000 @@ -117,7 +117,9 @@ || type == View.CHANGE_TOPCOMPONENT_ACTIVATED || type == View.CHANGE_DND_PERFORMED || type == View.CHANGE_UI_UPDATE - || type == View.CHANGE_PROJECT_NAME; + || type == View.CHANGE_PROJECT_NAME + || type == View.TOPCOMPONENT_CANCEL_REQUEST_ATTENTION + || type == View.TOPCOMPONENT_REQUEST_ATTENTION; synchronized(requests) { Object oldValue = null; Index: core/windows/src/org/netbeans/core/windows/WindowManagerImpl.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/WindowManagerImpl.java,v --- core/windows/src/org/netbeans/core/windows/WindowManagerImpl.java 9 Sep 2004 20:50:37 -0000 1.31 +++ core/windows/src/org/netbeans/core/windows/WindowManagerImpl.java 10 Sep 2004 18:54:52 -0000 @@ -100,7 +100,18 @@ } return (WindowManagerImpl)Lookup.getDefault().lookup(WindowManager.class); } + + public void topComponentRequestAttention(TopComponent tc) { + ModeImpl mode = (ModeImpl) findMode(tc); + + central.topComponentRequestAttention(mode, tc); + } + public void topComponentCancelRequestAttention(TopComponent tc) { + ModeImpl mode = (ModeImpl) findMode(tc); + + central.topComponentCancelRequestAttention(mode, tc); + } ///////////////////////// // API impelementation >> Index: core/windows/src/org/netbeans/core/windows/view/DefaultView.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/DefaultView.java,v --- core/windows/src/org/netbeans/core/windows/view/DefaultView.java 31 Aug 2004 13:35:00 -0000 1.36 +++ core/windows/src/org/netbeans/core/windows/view/DefaultView.java 10 Sep 2004 18:54:53 -0000 @@ -120,7 +120,8 @@ type != CHANGE_TOPCOMPONENT_DISPLAY_NAME_ANNOTATION_CHANGED && type != CHANGE_TOPCOMPONENT_TOOLTIP_CHANGED && type != CHANGE_TOPCOMPONENT_ICON_CHANGED && - type != CHANGE_PROJECT_NAME) { + type != CHANGE_PROJECT_NAME && + type != TOPCOMPONENT_REQUEST_ATTENTION) { isDangerous = true; break; } @@ -449,6 +450,37 @@ hierarchy.setMaximizedModeView(hierarchy.getModeViewForAccessor(wsa.getMaximizedModeAccessor())); hierarchy.updateDesktop(wsa); hierarchy.activateMode(wsa.getActiveModeAccessor()); + } else if (changeType == View.TOPCOMPONENT_REQUEST_ATTENTION) { + if (DEBUG) { + debugLog("Top component request attention"); + } + ModeView modeView = hierarchy.getModeViewForAccessor(wsa.findModeAccessor((String)viewEvent.getSource())); // XXX + if (modeView != null) { + TopComponent tc = (TopComponent) viewEvent.getNewValue(); + if (tc == null) { + throw new NullPointerException ("Top component is null for attention request"); //NOI18N + } + modeView.requestAttention (tc); + } else { + ErrorManager.getDefault().log(ErrorManager.INFORMATIONAL, + "Could not find mode " + viewEvent.getSource()); + } + } else if (changeType == View.TOPCOMPONENT_CANCEL_REQUEST_ATTENTION) { + if (DEBUG) { + debugLog("Top component cancel request attention"); //NOI18N + } + ModeView modeView = hierarchy.getModeViewForAccessor(wsa.findModeAccessor((String)viewEvent.getSource())); // XXX + if (modeView != null) { + TopComponent tc = (TopComponent) viewEvent.getNewValue(); + if (tc == null) { + throw new NullPointerException ("Top component is null for attention cancellation request"); //NOI18N + } + modeView.cancelRequestAttention (tc); + } else { + ErrorManager.getDefault().log(ErrorManager.INFORMATIONAL, + "Could not find mode " + viewEvent.getSource()); + } + } } Index: core/windows/src/org/netbeans/core/windows/view/ModeContainer.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/ModeContainer.java,v --- core/windows/src/org/netbeans/core/windows/view/ModeContainer.java 22 Apr 2004 13:09:47 -0000 1.3 +++ core/windows/src/org/netbeans/core/windows/view/ModeContainer.java 10 Sep 2004 18:54:54 -0000 @@ -52,5 +52,9 @@ public void updateToolTip(TopComponent tc); public void updateIcon(TopComponent tc); + + public void requestAttention(TopComponent tc); + + public void cancelRequestAttention(TopComponent tc); } Index: core/windows/src/org/netbeans/core/windows/view/ModeView.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/ModeView.java,v --- core/windows/src/org/netbeans/core/windows/view/ModeView.java 11 Jun 2004 08:44:00 -0000 1.11 +++ core/windows/src/org/netbeans/core/windows/view/ModeView.java 10 Sep 2004 18:54:54 -0000 @@ -144,6 +144,14 @@ public void updateIcon(TopComponent tc) { container.updateIcon(tc); } + + public void requestAttention (TopComponent tc) { + container.requestAttention(tc); + } + + public void cancelRequestAttention (TopComponent tc) { + container.cancelRequestAttention(tc); + } // XXX public void updateFrameState() { Index: core/windows/src/org/netbeans/core/windows/view/View.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/View.java,v --- core/windows/src/org/netbeans/core/windows/view/View.java 19 May 2004 18:56:41 -0000 1.5 +++ core/windows/src/org/netbeans/core/windows/view/View.java 10 Sep 2004 18:54:54 -0000 @@ -74,6 +74,9 @@ public int CHANGE_UI_UPDATE = 61; public int CHANGE_PROJECT_NAME = 62; + public int TOPCOMPONENT_REQUEST_ATTENTION = 63; + public int TOPCOMPONENT_CANCEL_REQUEST_ATTENTION = 64; + /** Provides GUI changes to manifest model changes to user. */ public void changeGUI(ViewEvent[] viewEvents, WindowSystemSnapshot snapshot); Index: core/windows/src/org/netbeans/core/windows/view/ViewEvent.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/ViewEvent.java,v --- core/windows/src/org/netbeans/core/windows/view/ViewEvent.java 13 Feb 2004 09:59:35 -0000 1.3 +++ core/windows/src/org/netbeans/core/windows/view/ViewEvent.java 10 Sep 2004 18:54:54 -0000 @@ -97,6 +97,8 @@ case View.CHANGE_TOPCOMPONENT_TOOLTIP_CHANGED : typeStr = "CHANGE_TOPCOMPONENT_TOOLTIP_CHANGED"; break; //NOI18N case View.CHANGE_UI_UPDATE : typeStr = "CHANGE_UI_UPDATE"; break; //NOI18N case View.CHANGE_VISIBILITY_CHANGED : typeStr = "CHANGE_VISIBILITY_CHANGED"; break; //NOI18N + case View.TOPCOMPONENT_REQUEST_ATTENTION : typeStr = "TOPCOMPONENT_REQUEST_ATTENTION"; break; //NOI18N + case View.TOPCOMPONENT_CANCEL_REQUEST_ATTENTION : typeStr = "TOPCOMPONENT_CANCEL_REQUEST_ATTENTION"; break; //NOI18N } buf.append(typeStr); buf.append("\nnewValue="); //NOI18N Index: core/windows/src/org/netbeans/core/windows/view/ui/DefaultSeparateContainer.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/ui/DefaultSeparateContainer.java,v --- core/windows/src/org/netbeans/core/windows/view/ui/DefaultSeparateContainer.java 24 Aug 2004 21:35:51 -0000 1.7 +++ core/windows/src/org/netbeans/core/windows/view/ui/DefaultSeparateContainer.java 10 Sep 2004 18:54:55 -0000 @@ -104,6 +104,14 @@ } }); } + + public void requestAttention (TopComponent tc) { + //not implemented + } + + public void cancelRequestAttention (TopComponent tc) { + //not implemented + } /** */ protected Component getModeComponent() { Index: core/windows/src/org/netbeans/core/windows/view/ui/DefaultSplitContainer.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/ui/DefaultSplitContainer.java,v --- core/windows/src/org/netbeans/core/windows/view/ui/DefaultSplitContainer.java 22 Apr 2004 13:09:51 -0000 1.5 +++ core/windows/src/org/netbeans/core/windows/view/ui/DefaultSplitContainer.java 10 Sep 2004 18:54:55 -0000 @@ -50,6 +50,14 @@ // To be able to move split dividers. panel.setMinimumSize(new Dimension(1, 1)); } + + public void requestAttention (TopComponent tc) { + tabbedHandler.requestAttention(tc); + } + + public void cancelRequestAttention (TopComponent tc) { + tabbedHandler.cancelRequestAttention(tc); + } /** */ protected Component getModeComponent() { Index: core/windows/src/org/netbeans/core/windows/view/ui/Tabbed.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/ui/Tabbed.java,v --- core/windows/src/org/netbeans/core/windows/view/ui/Tabbed.java 3 Jun 2004 12:07:29 -0000 1.8 +++ core/windows/src/org/netbeans/core/windows/view/ui/Tabbed.java 10 Sep 2004 18:54:55 -0000 @@ -31,6 +31,10 @@ */ public interface Tabbed { + public void requestAttention(TopComponent tc); + + public void cancelRequestAttention(TopComponent tc); + public void addTopComponent(String name, Icon icon, TopComponent tc, String toolTip); public void insertComponent(String name, Icon icon, Component comp, String toolTip, int position); Index: core/windows/src/org/netbeans/core/windows/view/ui/TabbedHandler.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/ui/TabbedHandler.java,v --- core/windows/src/org/netbeans/core/windows/view/ui/TabbedHandler.java 7 Sep 2004 13:10:14 -0000 1.13 +++ core/windows/src/org/netbeans/core/windows/view/ui/TabbedHandler.java 10 Sep 2004 18:54:55 -0000 @@ -102,7 +102,14 @@ return tabbed; } + + public void requestAttention (TopComponent tc) { + tabbed.requestAttention(tc); + } + public void cancelRequestAttention (TopComponent tc) { + tabbed.cancelRequestAttention(tc); + } public Component getComponent() { return tabbed.getComponent(); Index: core/windows/src/org/netbeans/core/windows/view/ui/slides/SlideBar.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/ui/slides/SlideBar.java,v --- core/windows/src/org/netbeans/core/windows/view/ui/slides/SlideBar.java 6 Sep 2004 13:46:57 -0000 1.14 +++ core/windows/src/org/netbeans/core/windows/view/ui/slides/SlideBar.java 10 Sep 2004 18:54:56 -0000 @@ -23,8 +23,10 @@ import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.DefaultSingleSelectionModel; @@ -219,10 +221,39 @@ int index = getButtonIndex(clickedButton); commandMgr.showPopup(mouseEvent, index); } + + private SlidingButton buttonFor (TopComponent tc) { + int idx = 0; + for (Iterator i=dataModel.getTabs().iterator(); i.hasNext();) { + TabData td = (TabData) i.next(); + if (td.getComponent() == tc) { + break; + } + if (!i.hasNext()) { + idx = -1; + } else { + idx++; + } + } + if (idx >= 0 && idx < dataModel.size()) { + return getButton(idx); + } else { + return null; + } + } + + public void setBlinking (TopComponent tc, boolean val) { + SlidingButton button = buttonFor (tc); + if (button != null) { + button.setBlinking(val); + } + } /** Triggers slide operation by changing selected index */ public void userClickedSlidingButton(Component clickedButton) { int index = getButtonIndex(clickedButton); + SlidingButton button = (SlidingButton) buttons.get(index); + button.setBlinking(false); if (index != selModel.getSelectedIndex() || !isActive()) { TopComponent tc = (TopComponent)dataModel.getTab(index).getComponent(); @@ -240,6 +271,8 @@ if (index < 0) { return false; } + SlidingButton button = (SlidingButton) buttons.get(index); + button.setBlinking(false); TopComponent tc = (TopComponent)dataModel.getTab(index).getComponent(); if (tc == null) { return false; @@ -340,9 +373,16 @@ private void syncWithModel () { assert SwingUtilities.isEventDispatchThread(); - + Set blinks = null; for (Iterator iter = buttons.iterator(); iter.hasNext(); ) { - gestureRecognizer.detachButton((SlidingButton)iter.next()); + SlidingButton curr = (SlidingButton) iter.next(); + if (curr.isBlinking()) { + if (blinks == null) { + blinks = new HashSet(); + } + blinks.add (curr.getData()); + } + gestureRecognizer.detachButton(curr); } removeAll(); buttons.clear(); @@ -350,7 +390,11 @@ List dataList = dataModel.getTabs(); SlidingButton curButton; for (Iterator iter = dataList.iterator(); iter.hasNext(); ) { - curButton = new SlidingButton((TabData)iter.next(), dataModel.getOrientation()); + TabData td = (TabData) iter.next(); + curButton = new SlidingButton(td, dataModel.getOrientation()); + if (blinks != null && blinks.contains(td)) { + curButton.setBlinking(true); + } gestureRecognizer.attachButton(curButton); buttons.add(curButton); add(curButton); Index: core/windows/src/org/netbeans/core/windows/view/ui/slides/SlideBarContainer.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/ui/slides/SlideBarContainer.java,v --- core/windows/src/org/netbeans/core/windows/view/ui/slides/SlideBarContainer.java 3 Jun 2004 12:07:30 -0000 1.3 +++ core/windows/src/org/netbeans/core/windows/view/ui/slides/SlideBarContainer.java 10 Sep 2004 18:54:56 -0000 @@ -48,6 +48,14 @@ panel.add(this.tabbedHandler.getComponent(), BorderLayout.CENTER); } + public void requestAttention (TopComponent tc) { + tabbedHandler.requestAttention(tc); + } + + public void cancelRequestAttention (TopComponent tc) { + tabbedHandler.cancelRequestAttention (tc); + } + public void setTopComponents(TopComponent[] tcs, TopComponent selected) { super.setTopComponents(tcs, selected); } Index: core/windows/src/org/netbeans/core/windows/view/ui/slides/TabbedSlideAdapter.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/ui/slides/TabbedSlideAdapter.java,v --- core/windows/src/org/netbeans/core/windows/view/ui/slides/TabbedSlideAdapter.java 3 Jun 2004 12:07:31 -0000 1.4 +++ core/windows/src/org/netbeans/core/windows/view/ui/slides/TabbedSlideAdapter.java 10 Sep 2004 18:54:56 -0000 @@ -69,6 +69,15 @@ selModel = new DefaultSingleSelectionModel(); slideBar = new SlideBar(this, (SlideBarDataModel)dataModel, selModel); } + + public void requestAttention (TopComponent tc) { + slideBar.setBlinking(tc, true); + } + + public void cancelRequestAttention (TopComponent tc) { + slideBar.setBlinking(tc, false); + } + private void setSide (String side) { int orientation = SlideBarDataModel.WEST; @@ -235,6 +244,11 @@ int newIndex = indexOf(comp); if (selModel.getSelectedIndex() != newIndex) { selModel.setSelectedIndex(newIndex); + } + if (comp instanceof TopComponent) { + //Inelegant to do this here, but it guarantees blinking stops + TopComponent tc = (TopComponent) comp; + tc.cancelRequestAttention(); } } Index: core/windows/src/org/netbeans/core/windows/view/ui/tabcontrol/TabbedAdapter.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/view/ui/tabcontrol/TabbedAdapter.java,v --- core/windows/src/org/netbeans/core/windows/view/ui/tabcontrol/TabbedAdapter.java 1 Sep 2004 12:56:30 -0000 1.22 +++ core/windows/src/org/netbeans/core/windows/view/ui/tabcontrol/TabbedAdapter.java 10 Sep 2004 18:54:57 -0000 @@ -83,6 +83,26 @@ int i = getSelectionModel().getSelectedIndex(); return i == -1 ? null : getTopComponentAt(i); } + + public void requestAttention (TopComponent tc) { + int idx = indexOf(tc); + if (idx >= 0) { + requestAttention(idx); + } else { + ErrorManager.getDefault().log(ErrorManager.INFORMATIONAL, + "RequestAttention on component unknown to container: " + tc); //NOI18N + } + } + + public void cancelRequestAttention (TopComponent tc) { + int idx = indexOf(tc); + if (idx >= 0) { + cancelRequestAttention(idx); + } else { + throw new IllegalArgumentException ("TopComponent " + tc + + " is not a child of this container"); //NOI18N + } + } public void insertComponent(String name, javax.swing.Icon icon, Component comp, String toolTip, int position) { TabData td = new TabData (comp, icon, name, toolTip); Index: openide/src/org/openide/windows/TopComponent.java =================================================================== RCS file: /cvs/openide/src/org/openide/windows/TopComponent.java,v --- openide/src/org/openide/windows/TopComponent.java 12 Aug 2004 07:38:38 -0000 1.132 +++ openide/src/org/openide/windows/TopComponent.java 10 Sep 2004 18:55:47 -0000 @@ -16,6 +16,7 @@ import java.awt.Image; import java.awt.Toolkit; import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.util.*; import java.beans.*; @@ -40,10 +41,12 @@ import javax.swing.JComponent; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; +import javax.swing.Timer; import javax.swing.text.Keymap; import org.openide.ErrorManager; import org.openide.awt.UndoRedo; +import org.openide.util.Mutex; import org.openide.util.actions.SystemAction; import org.openide.nodes.*; import org.openide.util.ContextAwareAction; @@ -550,6 +553,96 @@ public void requestVisible () { WindowManager.getDefault().topComponentRequestVisible(this); } + + /** + * Cause this TopComponent's tab to flash or otherwise draw attention to + * itself. This method is thread-safe. + *

+ * It will remain flashing until either cancelRequestAttention + * is called, the component becomes selected or its activated state changes, + * unless the brief argument is true, in which case it will stop + * after a few second. + * @param brief True if the tab should blink a few times and stop + * @since 4.45 + */ + public final void requestAttention(final boolean brief) { + //Reentrancy issues - always invoke later + SwingUtilities.invokeLater (new Runnable() { + public void run() { + if (attentionGetter != null && !brief) { + attentionGetter.kill(); + } else if (!brief) { + WindowManager.getDefault().topComponentRequestAttention( + TopComponent.this); + } else if (attentionGetter != null) { + attentionGetter.reset(); + } else { + attentionGetter = new AttentionGetter(); + } + } + }); + } + + /** + * Cause this TopComponent's tab to stop flashing if it was flashing. + * @since 4.45 + */ + public final void cancelRequestAttention() { + //Reentrancy issues - always invoke later + SwingUtilities.invokeLater (new Runnable() { + public void run() { + if (attentionGetter != null) { + attentionGetter.stop(); + } else { + WindowManager.getDefault().topComponentCancelRequestAttention( + TopComponent.this); + } + } + }); + } + + private AttentionGetter attentionGetter = null; + private class AttentionGetter implements ActionListener { + Timer timer = null; + public AttentionGetter () { + reset(); + } + + public void reset() { + assert SwingUtilities.isEventDispatchThread(); + if (timer != null) { + timer.stop(); + } + start(); + timer = new Timer (3500, this); + timer.setRepeats(false); + timer.start(); + } + + private void start() { + WindowManager.getDefault().topComponentRequestAttention( + TopComponent.this); + } + + public void kill() { + timer.stop(); + attentionGetter = null; + } + + private void stop() { + if (timer != null) { + timer.stop(); + } + attentionGetter = null; + WindowManager.getDefault().topComponentCancelRequestAttention( + TopComponent.this); + attentionGetter = null; + } + + public void actionPerformed (ActionEvent ae) { + stop(); + } + } /** Set the name of this top component. * The default implementation just notifies the window manager. Index: openide/src/org/openide/windows/WindowManager.java =================================================================== RCS file: /cvs/openide/src/org/openide/windows/WindowManager.java,v --- openide/src/org/openide/windows/WindowManager.java 3 Dec 2003 13:48:46 -0000 1.44 +++ openide/src/org/openide/windows/WindowManager.java 10 Sep 2004 18:55:48 -0000 @@ -365,6 +365,20 @@ * @since 4.13 */ protected abstract String topComponentID(TopComponent tc, String preferredID); + /** + * Cause this TopComponent's tab to flash or otherwise draw the users' attention + * to it. + * @param tc A TopComponent + * @since 4.45 */ + protected abstract void topComponentRequestAttention(TopComponent tc); + + /** + * Stop this TopComponent's tab from flashing if it is flashing. + * + * @param tc A TopComponent + * @since 4.45 */ + protected abstract void topComponentCancelRequestAttention(TopComponent tc); + /** Returns unique ID for specified TopComponent. * @param tc TopComponent the component for which is ID returned * @return unique TopComponent ID