diff --git a/platform/platform-api/platform-api.iml b/platform/platform-api/platform-api.iml index 3a4898f7c5cd..127cfb114b38 100644 --- a/platform/platform-api/platform-api.iml +++ b/platform/platform-api/platform-api.iml @@ -36,6 +36,7 @@ + diff --git a/platform/platform-api/src/com/intellij/ui/components/JBScrollPane.java b/platform/platform-api/src/com/intellij/ui/components/JBScrollPane.java index 72c85333b773..1754f711c7af 100644 --- a/platform/platform-api/src/com/intellij/ui/components/JBScrollPane.java +++ b/platform/platform-api/src/com/intellij/ui/components/JBScrollPane.java @@ -64,8 +64,10 @@ public class JBScrollPane extends JScrollPane { if (c == null) return null; if (!(c instanceof JViewport)) { - c = c.getParent(); - if (!(c instanceof JViewport)) return null; + // if asked for a viewport child, take a viewport. + // If not (e.g asked for a scrollbar), go straight to JLayeredPane + Container vp = c.getParent(); + if (vp instanceof JViewport) c = vp; } c = c.getParent(); diff --git a/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java b/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java index 6f79570d614c..384ad56dd92f 100644 --- a/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java +++ b/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java @@ -16,10 +16,13 @@ package com.intellij.util.ui; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.registry.Registry; import com.intellij.ui.Gray; import com.intellij.ui.JBColor; import com.intellij.ui.LightColors; +import com.intellij.ui.components.JBScrollPane; +import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.event.ChangeEvent; @@ -64,7 +67,8 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { private final MouseAdapter myMouseListener; private final HierarchyListener myHierarchyListener; private final AWTEventListener myAWTMouseListener; - private boolean myAWTMouseListenerAdded; + private final NSScrollerHelper.ScrollbarStyleListener myNSScrollerListener; + private boolean myGlobalListenersAdded; public static final int DELAY_FRAMES = 4; public static final int FRAMES_COUNT = 10 + DELAY_FRAMES; @@ -76,6 +80,7 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { private boolean myMouseOverScrollbar; private double myMouseOverScrollbarExpandLevel = 0; + private NSScrollerHelper.Style myMacScrollerStyle; private Animator myMouseOverScrollbarExpandAnimator; private Animator myMacScrollbarFadeAnimator; private double myMacScrollbarFadeLevel = 0; @@ -131,9 +136,15 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { myHierarchyListener = new HierarchyListener() { @Override public void hierarchyChanged(HierarchyEvent e) { - if (e.getChanged() == scrollbar - && (HierarchyEvent.DISPLAYABILITY_CHANGED & e.getChangeFlags()) != 0) { - updateAWTMotionListener(false); + if (e.getChanged() == scrollbar) { + if ((HierarchyEvent.DISPLAYABILITY_CHANGED & e.getChangeFlags()) != 0) { + updateGlobalListeners(false); + } + + if ((HierarchyEvent.SHOWING_CHANGED & e.getChangeFlags()) != 0) { + resetRegularThumbAnimator(); + resetMacScrollbarFadeout(); + } } } }; @@ -153,14 +164,38 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { } } }; - } - - protected static boolean isMacScrollbar() { - return UIUtil.isUnderNativeMacLookAndFeel() && !Registry.is("ui.no.mac.scrollbar"); + myNSScrollerListener = new NSScrollerHelper.ScrollbarStyleListener() { + @Override + public void styleChanged(@NotNull NSScrollerHelper.Style newStyle) { + updateMacScrollbarStyle(); + } + }; } + protected boolean isMacOverlayScrollbar() { + return myMacScrollerStyle == NSScrollerHelper.Style.Overlay; + } + + private void updateMacScrollbarStyle() { + if (!SystemInfo.isMac) return; + + NSScrollerHelper.Style style = UIUtil.isUnderNativeMacLookAndFeel() && !Registry.is("ui.no.mac.scrollbar") + ? NSScrollerHelper.getScrollerStyle() + : null; + + if (style != myMacScrollerStyle && scrollbar != null) { + myMacScrollerStyle = style; + JScrollBar scrollBar = scrollbar; + uninstallUI(scrollBar); + installUI(scrollBar); + + JScrollPane pane = JBScrollPane.findScrollPane(scrollBar); + if (pane != null) pane.revalidate(); + } + } + public boolean alwaysShowTrack() { - return !isMacScrollbar(); + return !isMacOverlayScrollbar(); } @Override @@ -192,7 +227,7 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { } private void resetRegularThumbAnimator() { - if (isMacScrollbar()) return; + if (isMacOverlayScrollbar()) return; myThumbFadeAnimator.reset(); if (scrollbar != null && scrollbar.getValueIsAdjusting() || myMouseIsOverThumb || Registry.is("ui.no.bangs.and.whistles")) { @@ -205,7 +240,7 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { } private void resetMacScrollbarExpandAnimator() { - if (!isMacScrollbar()) return; + if (!isMacOverlayScrollbar()) return; if (myMouseOverScrollbarExpandLevel == 0) { myMouseOverScrollbarExpandAnimator.reset(); @@ -217,7 +252,7 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { } private void resetMacScrollbarFadeout() { - if (!isMacScrollbar()) return; + if (!isMacOverlayScrollbar()) return; myMacScrollbarFadeAnimator.suspend(); myMacScrollbarFadeAnimator.reset(); @@ -246,6 +281,8 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { @Override protected void installDefaults() { + myMacScrollerStyle = NSScrollerHelper.getScrollerStyle(); + final int incGap = UIManager.getInt("ScrollBar.incrementButtonGap"); final int decGap = UIManager.getInt("ScrollBar.decrementButtonGap"); try { @@ -270,22 +307,25 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { scrollbar.addMouseMotionListener(myMouseMotionListener); scrollbar.addHierarchyListener(myHierarchyListener); - updateAWTMotionListener(false); - + updateGlobalListeners(false); + + resetRegularThumbAnimator(); resetMacScrollbarFadeout(); } - private void updateAWTMotionListener(boolean forceRemove) { + private void updateGlobalListeners(boolean forceRemove) { boolean shouldAdd = scrollbar.isDisplayable(); - if (myAWTMouseListenerAdded && (!shouldAdd || forceRemove)) { + if (myGlobalListenersAdded && (!shouldAdd || forceRemove)) { Toolkit.getDefaultToolkit().removeAWTEventListener(myAWTMouseListener); - myAWTMouseListenerAdded = false; + NSScrollerHelper.removeScrollbarStyleListener(myNSScrollerListener); + myGlobalListenersAdded = false; } - if (!myAWTMouseListenerAdded && shouldAdd && !forceRemove) { + if (!myGlobalListenersAdded && shouldAdd && !forceRemove) { Toolkit.getDefaultToolkit().addAWTEventListener(myAWTMouseListener, AWTEvent.MOUSE_MOTION_EVENT_MASK); - myAWTMouseListenerAdded = true; + NSScrollerHelper.addScrollbarStyleListener(myNSScrollerListener); + myGlobalListenersAdded = true; } } @@ -370,7 +410,7 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { scrollbar.removeMouseMotionListener(myMouseMotionListener); scrollbar.removeHierarchyListener(myHierarchyListener); - updateAWTMotionListener(true); + updateGlobalListeners(true); Disposer.dispose(myThumbFadeAnimator); Disposer.dispose(myMouseOverScrollbarExpandAnimator); @@ -384,7 +424,7 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { } protected int getThickness() { - return isMacScrollbar() ? 15 : 13; + return isMacOverlayScrollbar() ? 15 : 13; } @Override @@ -405,7 +445,7 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { @Override public boolean contains(JComponent c, int x, int y) { - if (!alwaysShowTrack() && myMacScrollbarHidden) return false; + if (isMacOverlayScrollbar() && !alwaysShowTrack() && myMacScrollbarHidden) return false; return super.contains(c, x, y); } @@ -418,7 +458,7 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { } protected void doPaintTrack(Graphics g, JComponent c, Rectangle bounds) { - if (isMacScrollbar() && !alwaysShowTrack()) { + if (isMacOverlayScrollbar() && !alwaysShowTrack()) { bounds = getMacScrollBarBounds(bounds, false); boolean vertical = isVertical(); @@ -456,7 +496,6 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { @Override protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) { - if (myMacScrollbarHidden) return; doPaintThumb(g, thumbBounds); } @@ -465,7 +504,7 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { return; } - if (isMacScrollbar()) { + if (isMacOverlayScrollbar()) { paintMacThumb(g, thumbBounds); } else { @@ -476,6 +515,8 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { } private void paintMacThumb(Graphics g, Rectangle thumbBounds) { + if (myMacScrollbarHidden) return; + thumbBounds = getMacScrollBarBounds(thumbBounds, true); Graphics2D g2d = (Graphics2D)g; RenderingHints oldHints = g2d.getRenderingHints(); @@ -573,7 +614,7 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { } protected Color adjustColor(Color c) { - if (isMacScrollbar()) { + if (isMacOverlayScrollbar()) { int alpha = (int)((120 + (myMouseOverScrollbarExpandLevel * 20)) * (1 - myMacScrollbarFadeLevel)); return new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha); } diff --git a/platform/platform-api/src/com/intellij/util/ui/NSScrollerHelper.java b/platform/platform-api/src/com/intellij/util/ui/NSScrollerHelper.java new file mode 100644 index 000000000000..5040073673f1 --- /dev/null +++ b/platform/platform-api/src/com/intellij/util/ui/NSScrollerHelper.java @@ -0,0 +1,116 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.util.ui; + +import com.intellij.openapi.util.SystemInfo; +import com.intellij.ui.mac.foundation.Foundation; +import com.intellij.ui.mac.foundation.ID; +import com.sun.jna.Callback; +import com.sun.jna.Pointer; +import org.jetbrains.annotations.NotNull; + +import javax.swing.event.EventListenerList; +import java.util.EventListener; + +import static com.intellij.ui.mac.foundation.Foundation.invoke; + +class NSScrollerHelper { + private static final Callback CALLBACK = new Callback() { + @SuppressWarnings("UnusedDeclaration") + public void callback(ID self, Pointer selector, ID event) { + final Style style = getScrollerStyle(); + UIUtil.invokeLaterIfNeeded(new Runnable() { + @Override + public void run() { + fireStyleChanged(style); + } + }); + } + }; + + public enum Style {Legacy, Overlay} + + private static final EventListenerList ourListeners = new EventListenerList(); + + static { + if (SystemInfo.isMac) { + initNotificationObserver(); + } + } + + private static void initNotificationObserver() { + Foundation.NSAutoreleasePool pool = new Foundation.NSAutoreleasePool(); + + ID delegateClass = Foundation.allocateObjcClassPair(Foundation.getObjcClass("NSObject"), "NSScrollerChangesObserver"); + if (!Foundation.addMethod(delegateClass, Foundation.createSelector("handleScrollerStyleChanged:"), CALLBACK, "v@")) { + throw new RuntimeException("Cannot add observer method"); + } + ; + Foundation.registerObjcClassPair(delegateClass); + ID delegate = invoke("NSScrollerChangesObserver", "new"); + + try { + ID defaultCenter = invoke("NSNotificationCenter", "defaultCenter"); + invoke(defaultCenter, "addObserver:selector:name:object:", + delegate, + Foundation.createSelector("handleScrollerStyleChanged:"), + Foundation.nsString("NSPreferredScrollerStyleDidChangeNotification"), + ID.NIL); + } + finally { + pool.drain(); + } + } + + @NotNull + public static Style getScrollerStyle() { + if (!SystemInfo.isMac) return Style.Legacy; + + Foundation.NSAutoreleasePool pool = new Foundation.NSAutoreleasePool(); + try { + if (Foundation.invoke(Foundation.getObjcClass("NSScroller"), "preferredScrollerStyle").intValue() == 1) { + return Style.Overlay; + } + } + catch (Throwable ignore) { + } + finally { + pool.drain(); + } + return Style.Legacy; + } + + public static void addScrollbarStyleListener(@NotNull ScrollbarStyleListener listener) { + ourListeners.add(ScrollbarStyleListener.class, listener); + } + + public static void removeScrollbarStyleListener(@NotNull ScrollbarStyleListener listener) { + ourListeners.remove(ScrollbarStyleListener.class, listener); + } + + private static void fireStyleChanged(@NotNull Style style) { + Object[] listeners = ourListeners.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == ScrollbarStyleListener.class) { + ((ScrollbarStyleListener)listeners[i + 1]).styleChanged(style); + } + } + } + + public interface ScrollbarStyleListener extends EventListener { + void styleChanged(@NotNull Style newStyle); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java index 333431dd5f1c..65f8ed660c18 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java @@ -528,7 +528,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark return; } - if (isMacScrollbar()) { + if (isMacOverlayScrollbar()) { if (!isMirrored()) { super.paintThumb(g, c, thumbBounds); } @@ -554,14 +554,14 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark @Override protected int adjustThumbWidth(int width) { - if (isMacScrollbar() || UISettings.getInstance().PRESENTATION_MODE) return super.adjustThumbWidth(width); + if (isMacOverlayScrollbar() || UISettings.getInstance().PRESENTATION_MODE) return super.adjustThumbWidth(width); return width - 2; } @Override protected int getThickness() { if (UISettings.getInstance().PRESENTATION_MODE) return super.getThickness(); - return super.getThickness() + (isMacScrollbar() ? 2 : 7); + return super.getThickness() + (isMacOverlayScrollbar() ? 2 : 7); } @Override @@ -618,7 +618,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark @Override protected Color adjustColor(Color c) { - if (isMacScrollbar()) return super.adjustColor(c); + if (isMacOverlayScrollbar()) return super.adjustColor(c); if (UIUtil.isUnderDarcula()) { return c;