From 3b28836cda0444928fb043f143f0f16e928b79c5 Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Thu, 2 Jul 2015 17:56:32 +0300 Subject: [PATCH] IDEA-76396, IDEA-91965 precise mouse/trackpad scrolling in editor on Mac --- .../openapi/editor/impl/EditorImpl.java | 3 + .../impl/PreciseMouseWheelScroller.java | 164 ++++++++++++++++++ .../util/resources/misc/registry.properties | 3 + 3 files changed, 170 insertions(+) create mode 100644 platform/platform-impl/src/com/intellij/openapi/editor/impl/PreciseMouseWheelScroller.java diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 32e2987f75cf..30fd0297d9d8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -516,6 +516,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myEditorComponent = new EditorComponentImpl(this); myScrollPane = new MyScrollPane(); + if (SystemInfo.isMac && SystemInfo.isJavaVersionAtLeast("1.7") && Registry.is("editor.mac.smooth.scrolling")) { + PreciseMouseWheelScroller.install(myScrollPane); + } myVerticalScrollBar = (MyScrollBar)myScrollPane.getVerticalScrollBar(); myVerticalScrollBar.setOpaque(false); myPanel = new JPanel(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/PreciseMouseWheelScroller.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/PreciseMouseWheelScroller.java new file mode 100644 index 000000000000..e3b446da58d8 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/PreciseMouseWheelScroller.java @@ -0,0 +1,164 @@ +/* + * Copyright 2000-2015 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.openapi.editor.impl; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.SystemInfo; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.lang.reflect.Method; + +/** + * Implements precise (with sub-line resolution) mouse wheel/trackpad scrolling in JScrollPane if platform supports it + * (currently known to work on Mac OS with Java 7 or later). + *

+ * Scroll pane's view is supposed to implement {@link Scrollable}. + * + * @see MouseWheelEvent#getPreciseWheelRotation() + */ +public class PreciseMouseWheelScroller implements MouseWheelListener { + private static final Logger LOGGER = Logger.getInstance(PreciseMouseWheelScroller.class); + private static Method PRECISE_WHEEL_ROTATION_GETTER; + + static { + if (SystemInfo.isJavaVersionAtLeast("1.7")) { + try { + Method method = MouseWheelEvent.class.getMethod("getPreciseWheelRotation"); + Class returnType = method.getReturnType(); + if (!returnType.equals(double.class)) throw new RuntimeException("Unexpected method return type: " + returnType); + PRECISE_WHEEL_ROTATION_GETTER = method; + } + catch (Exception e) { + LOGGER.warn("Couldn't access getPreciseWheelRotation method", e); + } + } + } + + public static void install(@NotNull JScrollPane scrollPane) { + scrollPane.setWheelScrollingEnabled(false); + scrollPane.addMouseWheelListener(new PreciseMouseWheelScroller(scrollPane)); + } + + private final JScrollPane myScrollPane; + + private PreciseMouseWheelScroller(JScrollPane scrollPane) { + myScrollPane = scrollPane; + } + + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + e.consume(); + if (e.getWheelRotation() != 0) { + int orientation = SwingConstants.VERTICAL; + JScrollBar toScroll = myScrollPane.getVerticalScrollBar(); + if (toScroll == null || !toScroll.isVisible() || e.isShiftDown()) { + orientation = SwingConstants.HORIZONTAL; + toScroll = myScrollPane.getHorizontalScrollBar(); + if (toScroll == null || !toScroll.isVisible()) { + return; + } + } + + int direction = e.getWheelRotation() < 0 ? -1 : 1; + + if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { + JViewport vp = myScrollPane.getViewport(); + Component comp = vp.getView(); + + boolean limitScroll = Math.abs(e.getWheelRotation()) == 1; + + Scrollable scrollComp = (Scrollable) comp; + Rectangle viewRect = vp.getViewRect(); + int scrollMin = toScroll.getMinimum(); + int scrollMax = toScroll.getMaximum() - toScroll.getModel().getExtent(); + + if (limitScroll) { + int blockIncr = scrollComp.getScrollableBlockIncrement(viewRect, orientation, direction); + if (direction < 0) { + scrollMin = Math.max(scrollMin, toScroll.getValue() - blockIncr); + } + else { + scrollMax = Math.min(scrollMax, toScroll.getValue() + blockIncr); + } + } + + double units = getUnitsToScroll(e); + double shift = scrollComp.getScrollableUnitIncrement(viewRect, orientation, direction) * units; + if (orientation == SwingConstants.VERTICAL) { + viewRect.y += shift; + if (direction < 0) { + if (viewRect.y <= scrollMin) { + viewRect.y = scrollMin; + } + } + else { + if (viewRect.y >= scrollMax) { + viewRect.y = scrollMax; + } + } + toScroll.setValue(viewRect.y); + } + else { + viewRect.x += shift; + if (direction < 0) { + if (viewRect.x < scrollMin) { + viewRect.x = scrollMin; + } + } + else { + if (viewRect.x > scrollMax) { + viewRect.x = scrollMax; + } + } + toScroll.setValue(viewRect.x); + } + } + else if (e.getScrollType() == MouseWheelEvent.WHEEL_BLOCK_SCROLL) { + int oldValue = toScroll.getValue(); + int blockIncrement = toScroll.getBlockIncrement(direction); + int delta = blockIncrement * direction; + int newValue = oldValue + delta; + + if (delta > 0 && newValue < oldValue) { + newValue = toScroll.getMaximum(); + } + else if (delta < 0 && newValue > oldValue) { + newValue = toScroll.getMinimum(); + } + + toScroll.setValue(newValue); + } + } + } + + // Required method was added in Java 7, but we need to compile for Java 6, so we resort to reflection + private static double getUnitsToScroll(MouseWheelEvent e) { + if (PRECISE_WHEEL_ROTATION_GETTER != null) { + try { + Double value = (Double)PRECISE_WHEEL_ROTATION_GETTER.invoke(e); + return e.getScrollAmount() * value; + } + catch (Exception ex) { + LOGGER.debug("Error calling getPreciseWheelRotation method", ex); + } + } + return e.getUnitsToScroll(); + } +} diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 12adcea3dfd7..90573c8892e3 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -144,6 +144,9 @@ editor.soft.wrap.force.limit.description=If document contains lines longer than editor.navigation.history.stack.size=25 editor.navigation.history.stack.size.description=Stack size limit for back/forward and last/next edit location navigation +editor.mac.smooth.scrolling=true +editor.mac.smooth.scrolling.description=Enable precise (with sub-line resolution) scrolling on Mac with mouse or trackpad + ide.showIndexRebuildMessage=false ide.tabbedPane.bufferedPaint=true