diff --git a/core.windows/manifest.mf b/core.windows/manifest.mf --- a/core.windows/manifest.mf +++ b/core.windows/manifest.mf @@ -6,5 +6,5 @@ OpenIDE-Module-Recommends: org.netbeans.core.windows.nativeaccess.NativeWindowSystem AutoUpdate-Show-In-Client: false AutoUpdate-Essential-Module: true -OpenIDE-Module-Specification-Version: 2.44 +OpenIDE-Module-Specification-Version: 2.45 diff --git a/core.windows/nbproject/project.xml b/core.windows/nbproject/project.xml --- a/core.windows/nbproject/project.xml +++ b/core.windows/nbproject/project.xml @@ -85,7 +85,7 @@ - 1.33 + 1.34 diff --git a/core.windows/src/org/netbeans/core/windows/Central.java b/core.windows/src/org/netbeans/core/windows/Central.java --- a/core.windows/src/org/netbeans/core/windows/Central.java +++ b/core.windows/src/org/netbeans/core/windows/Central.java @@ -1664,6 +1664,19 @@ mode, View.CHANGE_TOPCOMPONENT_ICON_CHANGED, null, tc)); } } + + /** + * + * @param mode + * @param tc + * @param busy + * @since 2.45 + */ + public void topComponentMakeBusy( ModeImpl mode, TopComponent tc, boolean busy ) { + String modeName = getModeName(mode); + viewRequestor.scheduleRequest ( + new ViewRequest(modeName, busy ? View.TOPCOMPONENT_SHOW_BUSY : View.TOPCOMPONENT_HIDE_BUSY, tc, tc)); + } public void resetModel() { model.reset(); diff --git a/core.windows/src/org/netbeans/core/windows/WindowManagerImpl.java b/core.windows/src/org/netbeans/core/windows/WindowManagerImpl.java --- a/core.windows/src/org/netbeans/core/windows/WindowManagerImpl.java +++ b/core.windows/src/org/netbeans/core/windows/WindowManagerImpl.java @@ -1919,6 +1919,31 @@ public void userStartedKeyboardDragAndDrop( TopComponentDraggable draggable ) { central.userStartedKeyboardDragAndDrop( draggable ); } + + private static final Object BUSY_FLAG = new Object(); + private static final String BUSY_PROP_NAME = "nbwinsys.tc.isbusy"; //NOI18N + + @Override + protected void topComponentMakeBusy( TopComponent tc, boolean busy ) { + boolean wasBusy = isTopComponentBusy( tc ); + tc.putClientProperty( BUSY_PROP_NAME, busy ? BUSY_FLAG : null ); + if( busy != wasBusy ) { + //update winsys + ModeImpl mode = (ModeImpl) findMode(tc); + if( null != mode ) + central.topComponentMakeBusy(mode, tc, busy); + } + } + + /** + * Check if the given TopComponent is 'busy' + * @param tc + * @return + * @since 2.45 + */ + public boolean isTopComponentBusy( TopComponent tc ) { + return tc.getClientProperty( BUSY_PROP_NAME ) == BUSY_FLAG; + } void fireEvent( WindowSystemEventType type ) { assertEventDispatchThread(); diff --git a/core.windows/src/org/netbeans/core/windows/view/DefaultView.java b/core.windows/src/org/netbeans/core/windows/view/DefaultView.java --- a/core.windows/src/org/netbeans/core/windows/view/DefaultView.java +++ b/core.windows/src/org/netbeans/core/windows/view/DefaultView.java @@ -482,6 +482,24 @@ Logger.getLogger(DefaultView.class.getName()).fine( "Could not find mode " + viewEvent.getSource()); } + } else if (changeType == View.TOPCOMPONENT_SHOW_BUSY || changeType == View.TOPCOMPONENT_HIDE_BUSY ) { + if (DEBUG) { + debugLog("Top component show/hide busy"); //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 make busy request"); //NOI18N + } + //make sure the TC is still opened in the given mode container + if( modeView.getTopComponents().contains( tc ) ) { + modeView.makeBusy(tc, changeType == View.TOPCOMPONENT_SHOW_BUSY); + } + } else { + Logger.getLogger(DefaultView.class.getName()).fine( + "Could not find mode " + viewEvent.getSource()); + } } else if (changeType == View.CHANGE_MAXIMIZE_TOPCOMPONENT_SLIDE_IN) { if (DEBUG) { debugLog("Slided-in top component toggle maximize"); //NOI18N diff --git a/core.windows/src/org/netbeans/core/windows/view/ModeContainer.java b/core.windows/src/org/netbeans/core/windows/view/ModeContainer.java --- a/core.windows/src/org/netbeans/core/windows/view/ModeContainer.java +++ b/core.windows/src/org/netbeans/core/windows/view/ModeContainer.java @@ -89,5 +89,7 @@ public void requestAttention(TopComponent tc); public void cancelRequestAttention(TopComponent tc); + + public void makeBusy(TopComponent tc, boolean busy); } diff --git a/core.windows/src/org/netbeans/core/windows/view/ModeView.java b/core.windows/src/org/netbeans/core/windows/view/ModeView.java --- a/core.windows/src/org/netbeans/core/windows/view/ModeView.java +++ b/core.windows/src/org/netbeans/core/windows/view/ModeView.java @@ -188,6 +188,10 @@ container.cancelRequestAttention(tc); } + public void makeBusy(TopComponent tc, boolean busy) { + container.makeBusy(tc, busy); + } + // XXX public void updateFrameState() { Component comp = container.getComponent(); diff --git a/core.windows/src/org/netbeans/core/windows/view/View.java b/core.windows/src/org/netbeans/core/windows/view/View.java --- a/core.windows/src/org/netbeans/core/windows/view/View.java +++ b/core.windows/src/org/netbeans/core/windows/view/View.java @@ -108,6 +108,10 @@ public int TOPCOMPONENT_REQUEST_ATTENTION = 63; public int TOPCOMPONENT_CANCEL_REQUEST_ATTENTION = 64; public int CHANGE_MAXIMIZE_TOPCOMPONENT_SLIDE_IN = 65; + + //toggle TopComponent busy + public int TOPCOMPONENT_SHOW_BUSY = 70; + public int TOPCOMPONENT_HIDE_BUSY = 71; /** Provides GUI changes to manifest model changes to user. */ public void changeGUI(ViewEvent[] viewEvents, WindowSystemSnapshot snapshot); diff --git a/core.windows/src/org/netbeans/core/windows/view/ViewEvent.java b/core.windows/src/org/netbeans/core/windows/view/ViewEvent.java --- a/core.windows/src/org/netbeans/core/windows/view/ViewEvent.java +++ b/core.windows/src/org/netbeans/core/windows/view/ViewEvent.java @@ -129,6 +129,8 @@ 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 + case View.TOPCOMPONENT_SHOW_BUSY : typeStr = "TOPCOMPONENT_SHOW_BUSY"; break; //NOI18N + case View.TOPCOMPONENT_HIDE_BUSY : typeStr = "TOPCOMPONENT_HIDE_BUSY"; break; //NOI18N } buf.append(typeStr); buf.append("\nnewValue="); //NOI18N diff --git a/core.windows/src/org/netbeans/core/windows/view/ui/DefaultSeparateContainer.java b/core.windows/src/org/netbeans/core/windows/view/ui/DefaultSeparateContainer.java --- a/core.windows/src/org/netbeans/core/windows/view/ui/DefaultSeparateContainer.java +++ b/core.windows/src/org/netbeans/core/windows/view/ui/DefaultSeparateContainer.java @@ -114,6 +114,11 @@ //not implemented } + @Override + public void makeBusy(TopComponent tc, boolean busy) { + tabbedHandler.makeBusy( tc, busy ); + } + /** */ @Override protected Component getModeComponent() { diff --git a/core.windows/src/org/netbeans/core/windows/view/ui/DefaultSplitContainer.java b/core.windows/src/org/netbeans/core/windows/view/ui/DefaultSplitContainer.java --- a/core.windows/src/org/netbeans/core/windows/view/ui/DefaultSplitContainer.java +++ b/core.windows/src/org/netbeans/core/windows/view/ui/DefaultSplitContainer.java @@ -95,6 +95,11 @@ tabbedHandler.cancelRequestAttention(tc); } + @Override + public void makeBusy(TopComponent tc, boolean busy) { + tabbedHandler.makeBusy( tc, busy ); + } + /** */ protected Component getModeComponent() { return panel; diff --git a/core.windows/src/org/netbeans/core/windows/view/ui/TabbedHandler.java b/core.windows/src/org/netbeans/core/windows/view/ui/TabbedHandler.java --- a/core.windows/src/org/netbeans/core/windows/view/ui/TabbedHandler.java +++ b/core.windows/src/org/netbeans/core/windows/view/ui/TabbedHandler.java @@ -143,6 +143,10 @@ public void cancelRequestAttention (TopComponent tc) { tabbed.cancelRequestAttention(tc); } + + public void makeBusy( TopComponent tc, boolean busy ) { + tabbed.makeBusy( tc, busy ); + } public Component getComponent() { return tabbed.getComponent(); diff --git a/core.windows/src/org/netbeans/core/windows/view/ui/slides/SlideBar.java b/core.windows/src/org/netbeans/core/windows/view/ui/slides/SlideBar.java --- a/core.windows/src/org/netbeans/core/windows/view/ui/slides/SlideBar.java +++ b/core.windows/src/org/netbeans/core/windows/view/ui/slides/SlideBar.java @@ -65,6 +65,7 @@ import org.netbeans.swing.tabcontrol.event.ComplexListDataEvent; import org.netbeans.swing.tabcontrol.event.ComplexListDataListener; import org.netbeans.swing.tabcontrol.event.TabActionEvent; +import org.netbeans.swing.tabcontrol.plaf.BusyTabsSupport; import org.netbeans.swing.tabcontrol.plaf.TabControlButton; import org.netbeans.swing.tabcontrol.plaf.TabControlButtonFactory; import org.openide.windows.Mode; @@ -465,7 +466,24 @@ if( gap > 0 ) addStrut(gap); } - + + void makeBusy( TopComponent tc, boolean busy ) { + BusyTabsSupport.getDefault().makeTabBusy( tabbed, 0, busy ); + syncWithModel(); + } + + @Override + public void addNotify() { + super.addNotify(); + BusyTabsSupport.getDefault().install( getTabbed(), dataModel ); + } + + @Override + public void removeNotify() { + super.removeNotify(); + BusyTabsSupport.getDefault().uninstall( getTabbed(), dataModel ); + } + private class SlidedWinsysInfoForTabbedContainer extends WinsysInfoForTabbedContainer { @Override public Object getOrientation(Component comp) { @@ -519,6 +537,11 @@ public boolean isSlidedOutContainer() { return true; } + + @Override + public boolean isTopComponentBusy( TopComponent tc ) { + return WindowManagerImpl.getInstance().isTopComponentBusy( tc ); + } } /*************** non public stuff **************************/ @@ -603,6 +626,10 @@ if (blinks != null && blinks.contains(td)) { curButton.setBlinking(true); } + TopComponent tc = ( TopComponent ) td.getComponent(); + if( tabbed.isBusy( tc ) ) { + curButton.setIcon( BusyTabsSupport.getDefault().getBusyIcon(false) ); + } String modeName = getRestoreModeNameForTab( td ); gestureRecognizer.attachButton(curButton); buttons.add(curButton); diff --git a/core.windows/src/org/netbeans/core/windows/view/ui/slides/SlideBarContainer.java b/core.windows/src/org/netbeans/core/windows/view/ui/slides/SlideBarContainer.java --- a/core.windows/src/org/netbeans/core/windows/view/ui/slides/SlideBarContainer.java +++ b/core.windows/src/org/netbeans/core/windows/view/ui/slides/SlideBarContainer.java @@ -105,7 +105,12 @@ @Override public void cancelRequestAttention (TopComponent tc) { tabbedHandler.cancelRequestAttention (tc); - } + } + + @Override + public void makeBusy(TopComponent tc, boolean busy) { + tabbedHandler.makeBusy( tc, busy ); + } @Override public void setTopComponents(TopComponent[] tcs, TopComponent selected) { diff --git a/core.windows/src/org/netbeans/core/windows/view/ui/slides/TabbedSlideAdapter.java b/core.windows/src/org/netbeans/core/windows/view/ui/slides/TabbedSlideAdapter.java --- a/core.windows/src/org/netbeans/core/windows/view/ui/slides/TabbedSlideAdapter.java +++ b/core.windows/src/org/netbeans/core/windows/view/ui/slides/TabbedSlideAdapter.java @@ -114,8 +114,17 @@ public void cancelRequestAttention (TopComponent tc) { slideBar.setBlinking(tc, false); } + + @Override + public void makeBusy( TopComponent tc, boolean busy ) { + slideBar.makeBusy( tc, busy ); + } + + @Override + public boolean isBusy( TopComponent tc ) { + return WindowManagerImpl.getInstance().isTopComponentBusy( tc ); + } - private void setSide (String side) { int orientation = SlideBarDataModel.WEST; if (Constants.LEFT.equals(side)) { diff --git a/core.windows/src/org/netbeans/core/windows/view/ui/tabcontrol/AbstractTabbedImpl.java b/core.windows/src/org/netbeans/core/windows/view/ui/tabcontrol/AbstractTabbedImpl.java --- a/core.windows/src/org/netbeans/core/windows/view/ui/tabcontrol/AbstractTabbedImpl.java +++ b/core.windows/src/org/netbeans/core/windows/view/ui/tabcontrol/AbstractTabbedImpl.java @@ -49,10 +49,7 @@ import java.util.logging.Logger; import javax.swing.*; import javax.swing.event.ChangeListener; -import org.netbeans.core.windows.Constants; -import org.netbeans.core.windows.Debug; -import org.netbeans.core.windows.ModeImpl; -import org.netbeans.core.windows.WindowManagerImpl; +import org.netbeans.core.windows.*; import org.netbeans.core.windows.actions.ActionUtils; import org.netbeans.swing.tabcontrol.ComponentConverter; import org.netbeans.swing.tabcontrol.TabData; @@ -421,6 +418,11 @@ cs.removeChangeListener( listener ); } + @Override + public boolean isBusy( TopComponent tc ) { + return WindowManagerImpl.getInstance().isTopComponentBusy( tc ); + } + protected abstract ComponentConverter getComponentConverter(); /** Returns instance of weak property change listener used to listen to diff --git a/core.windows/src/org/netbeans/core/windows/view/ui/tabcontrol/TabbedAdapter.java b/core.windows/src/org/netbeans/core/windows/view/ui/tabcontrol/TabbedAdapter.java --- a/core.windows/src/org/netbeans/core/windows/view/ui/tabcontrol/TabbedAdapter.java +++ b/core.windows/src/org/netbeans/core/windows/view/ui/tabcontrol/TabbedAdapter.java @@ -60,6 +60,7 @@ import org.netbeans.core.windows.Switches; import org.netbeans.swing.tabcontrol.*; import org.netbeans.swing.tabcontrol.event.TabActionEvent; +import org.netbeans.swing.tabcontrol.plaf.BusyTabsSupport; /** Adapter class that implements a pseudo JTabbedPane API on top * of the new tab control. This class should eventually be eliminated @@ -192,6 +193,11 @@ public boolean isModeSlidingEnabled() { return Switches.isModeSlidingEnabled(); } + + @Override + public boolean isTopComponentBusy( TopComponent tc ) { + return WindowManagerImpl.getInstance().isTopComponentBusy( tc ); + } } // end of LocInfo private final AbstractTabbedImpl tabbedImpl = new AbstractTabbedImpl() { @@ -306,5 +312,23 @@ protected Shape getDropIndication( TopComponent draggedTC, Point location ) { return TabbedAdapter.this.getDropIndication( draggedTC, location ); } + + @Override + public void makeBusy( TopComponent tc, boolean busy ) { + int tabIndex = indexOf( tc ); + BusyTabsSupport.getDefault().makeTabBusy( this, tabIndex, busy ); + } }; + + @Override + public void addNotify() { + super.addNotify(); + BusyTabsSupport.getDefault().install( getTabbed(), getModel() ); + } + + @Override + public void removeNotify() { + super.removeNotify(); + BusyTabsSupport.getDefault().uninstall( getTabbed(), getModel() ); + } } diff --git a/o.n.swing.tabcontrol/apichanges.xml b/o.n.swing.tabcontrol/apichanges.xml --- a/o.n.swing.tabcontrol/apichanges.xml +++ b/o.n.swing.tabcontrol/apichanges.xml @@ -108,6 +108,27 @@ + + + Display notification that a tab is 'busy'. + + + + + + The look and feel for tabbed containers has been extended to support + notifications that a tab is 'busy', i.e. some lengthy process is being + run in it.
+ All supported look and feel implementations use an animated icon to indicate + the busy state. +
+ + + + + +
+ Allow custom implementation of tab control. diff --git a/o.n.swing.tabcontrol/manifest.mf b/o.n.swing.tabcontrol/manifest.mf --- a/o.n.swing.tabcontrol/manifest.mf +++ b/o.n.swing.tabcontrol/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module-Localizing-Bundle: org/netbeans/swing/tabcontrol/Bundle.properties OpenIDE-Module: org.netbeans.swing.tabcontrol -OpenIDE-Module-Specification-Version: 1.33 +OpenIDE-Module-Specification-Version: 1.34 AutoUpdate-Essential-Module: true diff --git a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/TabDisplayerUI.java b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/TabDisplayerUI.java --- a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/TabDisplayerUI.java +++ b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/TabDisplayerUI.java @@ -49,16 +49,19 @@ package org.netbeans.swing.tabcontrol; -import org.netbeans.swing.tabcontrol.event.TabActionEvent; - -import javax.swing.*; -import javax.swing.plaf.ComponentUI; import java.awt.*; import java.awt.event.MouseEvent; import java.util.HashMap; import java.util.Map; +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.SingleSelectionModel; +import javax.swing.UIManager; +import javax.swing.plaf.ComponentUI; +import org.netbeans.swing.tabcontrol.event.TabActionEvent; import org.netbeans.swing.tabcontrol.plaf.TabControlButton; import org.netbeans.swing.tabcontrol.plaf.TabControlButtonFactory; +import org.openide.windows.TopComponent; /** * The basic UI of a tab displayer component. Defines the API of the UI for @@ -225,6 +228,26 @@ } /** + * Check if the given tab is busy and should be painted in a special way. + * @param tabIndex + * @return True if given tab is 'busy', false otherwise. + * @since 1.34 + */ + public final boolean isTabBusy( int tabIndex ) { + WinsysInfoForTabbedContainer winsysInfo = displayer.getContainerWinsysInfo(); + if( null == winsysInfo ) + return false; + TabDataModel model = displayer.getModel(); + if( tabIndex < 0 || tabIndex >= model.size() ) + return false; + TabData td = model.getTab( tabIndex ); + if( td.getComponent() instanceof TopComponent ) { + return winsysInfo.isTopComponentBusy( (TopComponent)td.getComponent() ); + } + return false; + } + + /** * Installs the selection model into the tab control via a package private * method. */ @@ -247,8 +270,8 @@ protected abstract void requestAttention (int tab); - protected abstract void cancelRequestAttention (int tab); - + protected abstract void cancelRequestAttention (int tab); + /** * @since 1.9 * @return An icon for various buttons displayed in tab control (close/pin/scroll left/right etc), see TabControlButton class. diff --git a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/WinsysInfoForTabbedContainer.java b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/WinsysInfoForTabbedContainer.java --- a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/WinsysInfoForTabbedContainer.java +++ b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/WinsysInfoForTabbedContainer.java @@ -132,10 +132,21 @@ return false; } + /** + * Check if the header of given TopComponent should be painted in a special + * way to indicate that some process is running in that TopComponent. + * @param tc + * @return True to indicate process being run in the TopComponent, false otherwise. + * @since 1.34 + */ + public boolean isTopComponentBusy( TopComponent tc ) { + return false; + } + public static WinsysInfoForTabbedContainer getDefault( WinsysInfoForTabbed winsysInfo ) { return new DefaultWinsysInfoForTabbedContainer( winsysInfo ); } - + private static class DefaultWinsysInfoForTabbedContainer extends WinsysInfoForTabbedContainer { private WinsysInfoForTabbed delegate; diff --git a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/customtabs/Tabbed.java b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/customtabs/Tabbed.java --- a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/customtabs/Tabbed.java +++ b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/customtabs/Tabbed.java @@ -141,6 +141,27 @@ public abstract boolean isTransparent(); public abstract void setTransparent( boolean transparent ); + + /** + * Notify user that given TopComponent is 'busy' (some lengthy process is + * running in it). + * @param tc + * @param busy True to make the TopComponent busy, false to cancel the notification. + * @since 1.34 + */ + public void makeBusy( TopComponent tc, boolean busy ) { + } + + /** + * Check if given TopComponent is busy + * @param tc + * @return True if the TopComponent is busy and its header should be painted + * in a special way, false otherwise. + * @since 1.34 + */ + public boolean isBusy( TopComponent tc ) { + return false; + } /** * Visual containers that hold the tabbed components must implement this interface diff --git a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java --- a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java +++ b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java @@ -268,6 +268,13 @@ return (state & TabState.AFTER_SELECTED) != 0; } + /** + * @return True if the tab is busy. + */ + protected final boolean isBusy() { + return (state & TabState.BUSY) != 0; + } + public Dimension getPadding() { return new Dimension(padding); } diff --git a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java --- a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java +++ b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java @@ -92,6 +92,9 @@ @Override protected void paintIconAndText(Graphics g) { + if( isBusy() ) { + setIcon( BusyTabsSupport.getDefault().getBusyIcon( isSelected() ) ); + } super.paintIconAndText(g); } diff --git a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java --- a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java +++ b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java @@ -64,6 +64,7 @@ import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.UIManager; +import org.netbeans.swing.tabcontrol.WinsysInfoForTabbedContainer; /** * A view tabs ui for OS-X adapted from the view tabs UI for Metal. @@ -165,6 +166,17 @@ textY = (height / 2) - (textHeight / 2) + fm.getAscent(); } + boolean slidedOut = false; + WinsysInfoForTabbedContainer winsysInfo = displayer.getContainerWinsysInfo(); + if( null != winsysInfo && winsysInfo.isSlidedOutContainer() ) + slidedOut = false; + if( isTabBusy( index ) && !slidedOut ) { + Icon busyIcon = BusyTabsSupport.getDefault().getBusyIcon( isSelected( index ) ); + textW -= busyIcon.getIconWidth() - 3 - TXT_X_PAD; + busyIcon.paintIcon( displayer, g, textX, y+(height-busyIcon.getIconHeight())/2); + textX += busyIcon.getIconWidth() + 3; + } + int realTextWidth = (int)HtmlRenderer.renderString(text, g, textX, textY, textW, height, getTxtFont(), UIManager.getColor("textText"), //NOI18N HtmlRenderer.STYLE_TRUNCATE, false); diff --git a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicTabDisplayerUI.java b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicTabDisplayerUI.java --- a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicTabDisplayerUI.java +++ b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicTabDisplayerUI.java @@ -70,6 +70,7 @@ import javax.swing.event.ListDataEvent; import org.netbeans.swing.tabcontrol.TabData; import org.netbeans.swing.tabcontrol.TabDisplayer; +import org.netbeans.swing.tabcontrol.WinsysInfoForTabbedContainer; import org.netbeans.swing.tabcontrol.event.ComplexListDataEvent; import org.openide.windows.TopComponent; @@ -427,6 +428,10 @@ TabCellRenderer ren = getTabCellRenderer(i); TabData data = displayer.getModel().getTab(i); + + if( isTabBusy( i ) ) { + state |= TabState.BUSY; + } JComponent renderer = ren.getRendererComponent( data, scratch, state); diff --git a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BusyIcon.java b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BusyIcon.java new file mode 100644 --- /dev/null +++ b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BusyIcon.java @@ -0,0 +1,210 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.swing.tabcontrol.plaf; + +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.geom.AffineTransform; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.Icon; +import javax.swing.UIManager; +import org.openide.util.ImageUtilities; +import org.openide.util.Lookup; + +/** + * An animated icon to indicate that a tab is 'busy'. + * @see BusyTabsSupport + * @author S. Aubrecht + * @since 1.34 + */ +abstract class BusyIcon implements Icon { + + protected final int width; + protected final int height; + + protected BusyIcon( int width, int height ) { + this.width = width; + this.height = height; + } + + /** + *

Creates a new instance.

+ *

+ * The implementation first checks UIManager defaults and looks for Icon + * under keys "nb.tabcontrol.busy.icon.selected" and "nb.tabcontrol.busy.icon.normal". + * If there is an Icon under those keys then the created instance will rotate + * that Icon to animate it. + *

+ * If there are no Icons in UIManager then there will be an attempt to create + * animated Icon based BusyPainter in SwingX library. If swingx.jar + * is available on classpath then reflection is used to create BusyPainter + * instance and paint icon animations with it. + *

+ * If SwingX library isn't available then the default image + * "org/netbeans/swing/tabcontrol/resources/busy_icon.png" + * will be rotated. + *

+ * + * @param selectedTab Boolean to create icon for selected tab state, false + * to create icon for normal tab state. + * @return Animated icon. + */ + public static BusyIcon create( boolean selectedTab ) { + BusyIcon res = null; + Icon img = UIManager.getIcon( "nb.tabcontrol.busy.icon." + (selectedTab ? ".selected" : ".normal") ); //NOI18N + if( null != img ) { + res = new ImageBusyIcon( ImageUtilities.icon2Image( img ) ); + } else { + res = SwingXBusyIcon.create(); + } + if( null == res ) + res = new ImageBusyIcon( ImageUtilities.loadImage( "org/netbeans/swing/tabcontrol/resources/busy_icon.png") ); //NOI18N + return res; + } + + abstract void tick(); + + @Override + public final int getIconWidth() { + return width; + } + + @Override + public final int getIconHeight() { + return height; + } + + private static class ImageBusyIcon extends BusyIcon { + + private final Image img; + private int state = 0; + private AffineTransform at; + private static final int STEP = 15; + + public ImageBusyIcon( Image img ) { + super( img.getWidth( null ), img.getHeight( null ) ); + this.img = img; + } + + @Override + void tick() { + state += STEP; + if( state >= 360 ) + state = 0; + at = new AffineTransform(); + at.rotate( state * Math.PI / 180.0, width/2, height/2 ); + } + + @Override + public void paintIcon( Component c, Graphics g, int x, int y ) { + if( g instanceof Graphics2D ) { + Graphics2D g2d = ( Graphics2D ) g; + g2d.translate( x, y ); + g2d.drawImage( img, at, null ); + g2d.translate( -x, -y ); + } + } + } + + private static class SwingXBusyIcon extends BusyIcon { + + private final Object painter; + private final Method setFrameMethod; + private final Method paintMethod; + private int currentFrame = 0; + private static final int POINTS = 8; + private static final int HEIGHT = 14; + + private SwingXBusyIcon( Object painter, Method paint, Method setFrame ) { + super( HEIGHT, HEIGHT ); + this.painter = painter; + this.setFrameMethod = setFrame; + this.paintMethod = paint; + } + + public static BusyIcon create() { + Object painter = null; + ClassLoader cl = Lookup.getDefault().lookup( ClassLoader.class ); + try { + Class painterClass = cl.loadClass( "org.jdesktop.swingx.painter.BusyPainter" ); //NOI18N + Constructor ctor = painterClass.getConstructor( int.class ); + painter = ctor.newInstance( HEIGHT ); + Method setFrame = painterClass.getMethod( "setFrame", int.class ); //NOI18N + Method paint = painterClass.getMethod( "paint", Graphics2D.class, Object.class, int.class, int.class ); //NOI18N + Method m = painterClass.getMethod( "setPoints", int.class ); //NOI18N + m.invoke( painter, POINTS ); + return new SwingXBusyIcon( painter, paint, setFrame ); + } catch( Exception ex ) { + Logger.getLogger( BusyIcon.class.getName() ).log( Level.FINE, null, ex ); + } + return null; + } + + @Override + public void tick() { + currentFrame = (currentFrame + 1) % POINTS; + try { + setFrameMethod.invoke( painter, currentFrame ); + } catch( Exception ex ) { + } + } + + @Override + public void paintIcon( Component c, Graphics g, int x, int y ) { + if( g instanceof Graphics2D ) { + Graphics2D g2d = ( Graphics2D ) g; + try { + g2d.translate( x, y ); + paintMethod.invoke( painter, g, c, x, y ); + } catch( Exception ex ) { + Logger.getLogger( BusyIcon.class.getName() ).log( Level.FINE, null, ex ); + } + g2d.translate( -x, -y ); + } + } + } +} diff --git a/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BusyTabsSupport.java b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BusyTabsSupport.java new file mode 100644 --- /dev/null +++ b/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BusyTabsSupport.java @@ -0,0 +1,277 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.swing.tabcontrol.plaf; + +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Set; +import javax.swing.Icon; +import javax.swing.Timer; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.netbeans.swing.tabcontrol.TabDataModel; +import org.netbeans.swing.tabcontrol.customtabs.Tabbed; +import org.openide.util.Lookup; +import org.openide.util.WeakSet; +import org.openide.util.lookup.ServiceProvider; +import org.openide.windows.TopComponent; + +/** + * Support class to implement animation in tab headers to indicate some sort of + * 'busy' state.
install(Tabbed, TabDataModel)) and if any tab is marked as 'busy' + * (TopComponent.makeBusy(boolean)) it forces repeated repaints of + * that tab to allow animation effects. The UI of the tab container then can use + * an icon this class provides (getBusyIcon(boolean))to indicate + * the busy state. The default implementation of this class ensures that the + * icon is properly animated in each repaint. + * + * @see TopComponent#makeBusy(boolean) + * + * @author S. Aubrecht + */ +public abstract class BusyTabsSupport { + + private Timer animationTimer; + + private final ChangeListener modelListener = new ChangeListener() { + + @Override + public void stateChanged( ChangeEvent e ) { + checkBusyTabs(); + } + }; + + private final Set containers = new WeakSet(10); + private final Set busyContainers = new WeakSet(10); + + /** + * @return The default implementation registered in global Lookup. + * @see DefaultBusyTabsSupport + */ + public static BusyTabsSupport getDefault() { + return Lookup.getDefault().lookup( BusyTabsSupport.class ); + } + + /** + * Track changes in given tab container to have busy tab headers repainted. + * @param tabContainer Tab container. This method should be typically called + * from container's addNotify(). + * @param tabModel Container data model. + */ + public final void install( Tabbed tabContainer, TabDataModel tabModel ) { + if( containers.contains( tabContainer ) ) + return; + tabModel.addChangeListener( modelListener ); + containers.add( tabContainer ); + checkBusyTabs(); + } + + /** + * Unregister the given tab container. This method should be typically called + * from container's removeNotify(). + * + * @param tabContainer + * @param tabModel + */ + public final void uninstall( Tabbed tabContainer, TabDataModel tabModel ) { + if( busyContainers.remove( tabContainer ) ) + repaintAll( tabContainer ); + tabModel.removeChangeListener( modelListener ); + containers.remove( tabContainer ); + checkBusyTabs(); + } + + /** + * An icon that can be shown in busy tab's header to indicate some lengthy + * process is being run in it. The default implementation ensures the icon + * is properly animated. + * @param isTabSelected True to get icon for a selected tab, false to get + * icon for regular tab state. + * @return Busy-like icon. + */ + public abstract Icon getBusyIcon( boolean isTabSelected ); + + /** + * Notification that busy state has changed for a given tab. + * @param tabContainer Tab container. + * @param tabIndex Index of the tab. + * @param busy + */ + public final void makeTabBusy( Tabbed tabContainer, int tabIndex, boolean busy ) { + if( !busy ) { + Rectangle tabRect = tabContainer.getTabBounds( tabIndex ); + tabContainer.getComponent().repaint( tabRect.x, tabRect.y, tabRect.width, tabRect.height ); + } + checkBusyTabs(); + } + + /** + * + * @return The time in milliseconds between repaints of busy tabs. Value of + * zero means there are no repeated repaints. + */ + protected abstract int getRepaintTimerIntervalMillis(); + + /** + * Invoked between each repaint events. The default implementation animates + * the busy icon in this method. + */ + protected abstract void tick(); + + private void startAnimationTimer() { + if( null != animationTimer ) { + return; + } + int interval = getRepaintTimerIntervalMillis(); + if( interval <= 0 ) + return; + animationTimer = new Timer( interval, new ActionListener() { + @Override + public void actionPerformed( ActionEvent e ) { + checkBusyTabs(); + repaintBusyTabs(); + tick(); + } + }); + animationTimer.setRepeats( true ); + animationTimer.start(); + } + + private void stopAnimationTimer() { + if( null == animationTimer ) { + return; + } + animationTimer.stop(); + animationTimer = null; + repaintBusyTabs(); + } + + private void checkBusyTabs() { + busyContainers.clear(); + + for( Tabbed tc : containers ) { + if( hasBusyTabs( tc ) ) + busyContainers.add( tc ); + } + + if( busyContainers.isEmpty() ) { + stopAnimationTimer(); + } else { + startAnimationTimer(); + } + } + + private void repaintBusyTabs() { + for( Tabbed tc : busyContainers ) { + repaintBusy( tc ); + } + } + + private void repaintBusy( Tabbed tabbed ) { + for( int i=0; i#>hky!;IgqZ);iY8CLx|=d8WI@9eeL z-g#?pFzXO4JxAarHK8!?B^b*7jtLm{20^?DyLQm5z>3e{G|r0#Jum>HuwzV`3GqBk zY5tEv9gg5pCSfJxa0xd~d=JY|@q1uR-ry6KU_m8jUK}D%bfFKrq2}_l>O{pRyI>!B zP3{g(61#s3=dj}2SaK7a9a{M16R8sVWm0$fY5nY>-9d`tZCH0@g#&f~7ckS#Ac`kp zOa>cJR!80?{$)}m#LsZ3F3iEA47T9Pn?!>T)ojJKQ-yMCa1B*>je{W?aI7;>OYDBG rXArLHj#RBdG->?fHa^*pZvh4Xx%ov%((@F#00000NkvXXu0mjf2hyg} diff --git a/openide.windows/apichanges.xml b/openide.windows/apichanges.xml --- a/openide.windows/apichanges.xml +++ b/openide.windows/apichanges.xml @@ -50,6 +50,21 @@ Window System API + + + Added method TopComponent.makeBusy(boolean). + + + + + +

The new method can be used to inform user that some (possibly lengthy) + process is being run in given TopComponent.

+
+ + + +
Added branding options to replace the custom TabbedContainer with diff --git a/openide.windows/manifest.mf b/openide.windows/manifest.mf --- a/openide.windows/manifest.mf +++ b/openide.windows/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.openide.windows -OpenIDE-Module-Specification-Version: 6.50 +OpenIDE-Module-Specification-Version: 6.51 OpenIDE-Module-Localizing-Bundle: org/openide/windows/Bundle.properties AutoUpdate-Essential-Module: true diff --git a/openide.windows/src/org/openide/windows/TopComponent.java b/openide.windows/src/org/openide/windows/TopComponent.java --- a/openide.windows/src/org/openide/windows/TopComponent.java +++ b/openide.windows/src/org/openide/windows/TopComponent.java @@ -95,15 +95,7 @@ import org.openide.nodes.Node; import org.openide.nodes.NodeAdapter; import org.openide.nodes.NodeListener; -import org.openide.util.ContextAwareAction; -import org.openide.util.HelpCtx; -import org.openide.util.ImageUtilities; -import org.openide.util.Lookup; -import org.openide.util.NbBundle; -import org.openide.util.NbPreferences; -import org.openide.util.Utilities; -import org.openide.util.WeakListeners; -import org.openide.util.WeakSet; +import org.openide.util.*; import org.openide.util.actions.NodeAction; import org.openide.util.actions.SystemAction; @@ -922,6 +914,25 @@ } /** + * Notify the user that some (possibly lengthy) process is being run in this + * window. + * It is safe to call this method outside EDT. + * + * @param True to start 'busy' notification, 'false' to stop it. + * + * @see WindowManager#topComponentMakeBusy(org.openide.windows.TopComponent, boolean) + * @since 6.51 + */ + public final void makeBusy( final boolean busy ) { + Mutex.EVENT.readAccess( new Runnable() { + @Override + public void run() { + WindowManager.getDefault().topComponentMakeBusy( TopComponent.this, busy ); + } + }); + } + + /** * Cause this TopComponent's tab to stop flashing if it was flashing. * @since 5.1 */ diff --git a/openide.windows/src/org/openide/windows/WindowManager.java b/openide.windows/src/org/openide/windows/WindowManager.java --- a/openide.windows/src/org/openide/windows/WindowManager.java +++ b/openide.windows/src/org/openide/windows/WindowManager.java @@ -488,6 +488,19 @@ } /** + * Notifies the user that some process is running in the given TopComponent, + * for example by drawing an animated "wait" icon in TopComponent's header.
+ * The default implementation does nothing. + * + * @param tc + * @param busy True to start 'busy' notification, false to stop it. + * + * @since 6.51 + */ + protected void topComponentMakeBusy( TopComponent tc, boolean busy ) { + } + + /** * Attempts to bring the parent Window of the given TopComponent * to front of other windows. * @see java.awt.Window#toFront()