[console] allow smart scrolling in console 73/21073/5
Stéphane MOTTELET [Mon, 26 Aug 2019 09:57:02 +0000 (11:57 +0200)]
There may be times when output is dynamically added to the console
(optimization log or such). Normally, you would like the console to
scroll to the bottom automatically as new data is added so you can see
the most recent data. However, there may also be times when you are
viewing data somewhere else in the viewport, e.g. by dragging the
scrollbar of the console, and you don’t want scrolling to happen
automatically. This is implemented in this patch.

Source coming from:
https://tips4java.wordpress.com/2013/03/03/smart-scrolling/) with no
copyright (see https://tips4java.wordpress.com/about/).

Change-Id: Id245931789922fe8268ee37c831e1175bfbe8397

scilab/modules/console/src/java/org/scilab/modules/console/SciConsole.java
scilab/modules/console/src/java/org/scilab/modules/console/SciOutputView.java
scilab/modules/console/src/java/org/scilab/modules/console/SmartScroller.java [new file with mode: 0644]

index ea29635..78991be 100644 (file)
@@ -22,6 +22,10 @@ import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
 import java.io.IOException;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -219,6 +223,10 @@ public abstract class SciConsole extends JPanel {
             }
         });
 
+        // Allows to disable autoscrolling of console when scrollbar has been moved up, and re-enable it when
+        // scrollbar is moved down to the end again.
+        new SmartScroller(jSP);
+
         sciConsole.invalidate();
         sciConsole.doLayout();
     }
index 33f1ebe..050d619 100644 (file)
@@ -294,10 +294,6 @@ public class SciOutputView extends JEditorPane implements OutputView, ViewFactor
                 e.printStackTrace();
             }
         }
-
-        if (console != null) {
-            console.updateScrollPosition();
-        }
     }
 
     /**
diff --git a/scilab/modules/console/src/java/org/scilab/modules/console/SmartScroller.java b/scilab/modules/console/src/java/org/scilab/modules/console/SmartScroller.java
new file mode 100644 (file)
index 0000000..d48a314
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
+ *
+ * Copyright (C) 2013 - Rob CAMICK
+ *
+ * This file is hereby licensed under the terms of the GNU GPL v2.0,
+ * For more information, see the COPYING file which you should have received
+ * along with this program.
+ *
+ */
+
+package org.scilab.modules.console;
+
+import java.awt.Component;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.text.*;
+
+/**
+ *
+ * * https://tips4java.wordpress.com/2013/03/03/smart-scrolling/ *
+ *
+ *  The SmartScroller will attempt to keep the viewport positioned based on
+ *  the users interaction with the scrollbar. The normal behaviour is to keep
+ *  the viewport positioned to see new data as it is dynamically added.
+ *
+ *  Assuming vertical scrolling and data is added to the bottom:
+ *
+ *  - when the viewport is at the bottom and new data is added,
+ *    then automatically scroll the viewport to the bottom
+ *  - when the viewport is not at the bottom and new data is added,
+ *    then do nothing with the viewport
+ *
+ *  Assuming vertical scrolling and data is added to the top:
+ *
+ *  - when the viewport is at the top and new data is added,
+ *    then do nothing with the viewport
+ *  - when the viewport is not at the top and new data is added, then adjust
+ *    the viewport to the relative position it was at before the data was added
+ *
+ *  Similiar logic would apply for horizontal scrolling.
+ */
+
+public class SmartScroller implements AdjustmentListener
+{
+    public final static int HORIZONTAL = 0;
+    public final static int VERTICAL = 1;
+
+    public final static int START = 0;
+    public final static int END = 1;
+
+    private int viewportPosition;
+
+    private JScrollBar scrollBar;
+    private boolean adjustScrollBar = true;
+
+    private int previousValue = -1;
+    private int previousMaximum = -1;
+
+    /**
+     *  Convenience constructor.
+     *  Scroll direction is VERTICAL and viewport position is at the END.
+     *
+     *  @param scrollPane the scroll pane to monitor
+     */
+    public SmartScroller(JScrollPane scrollPane)
+    {
+        this(scrollPane, VERTICAL, END);
+    }
+
+    /**
+     *  Convenience constructor.
+     *  Scroll direction is VERTICAL.
+     *
+     *  @param scrollPane the scroll pane to monitor
+     *  @param viewportPosition valid values are START and END
+     */
+    public SmartScroller(JScrollPane scrollPane, int viewportPosition)
+    {
+        this(scrollPane, VERTICAL, viewportPosition);
+    }
+
+    /**
+     *  Specify how the SmartScroller will function.
+     *
+     *  @param scrollPane the scroll pane to monitor
+     *  @param scrollDirection indicates which JScrollBar to monitor.
+     *                         Valid values are HORIZONTAL and VERTICAL.
+     *  @param viewportPosition indicates where the viewport will normally be
+     *                          positioned as data is added.
+     *                          Valid values are START and END
+     */
+    public SmartScroller(JScrollPane scrollPane, int scrollDirection, int viewportPosition)
+    {
+        if (scrollDirection != HORIZONTAL
+        &&  scrollDirection != VERTICAL)
+            throw new IllegalArgumentException("invalid scroll direction specified");
+
+        if (viewportPosition != START
+        &&  viewportPosition != END)
+            throw new IllegalArgumentException("invalid viewport position specified");
+
+        this.viewportPosition = viewportPosition;
+
+        if (scrollDirection == HORIZONTAL)
+            scrollBar = scrollPane.getHorizontalScrollBar();
+        else
+            scrollBar = scrollPane.getVerticalScrollBar();
+
+        scrollBar.addAdjustmentListener( this );
+
+        //  Turn off automatic scrolling for text components
+
+        Component view = scrollPane.getViewport().getView();
+
+        if (view instanceof JTextComponent)
+        {
+            JTextComponent textComponent = (JTextComponent)view;
+            DefaultCaret caret = (DefaultCaret)textComponent.getCaret();
+            caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
+        }
+    }
+
+    @Override
+    public void adjustmentValueChanged(final AdjustmentEvent e)
+    {
+        SwingUtilities.invokeLater(new Runnable()
+        {
+            public void run()
+            {
+                checkScrollBar(e);
+            }
+        });
+    }
+
+    /*
+     *  Analyze every adjustment event to determine when the viewport
+     *  needs to be repositioned.
+     */
+    private void checkScrollBar(AdjustmentEvent e)
+    {
+        //  The scroll bar listModel contains information needed to determine
+        //  whether the viewport should be repositioned or not.
+
+        JScrollBar scrollBar = (JScrollBar)e.getSource();
+        BoundedRangeModel listModel = scrollBar.getModel();
+        int value = listModel.getValue();
+        int extent = listModel.getExtent();
+        int maximum = listModel.getMaximum();
+
+        boolean valueChanged = previousValue != value;
+        boolean maximumChanged = previousMaximum != maximum;
+
+        //  Check if the user has manually repositioned the scrollbar
+
+        if (valueChanged && !maximumChanged)
+        {
+            if (viewportPosition == START)
+                adjustScrollBar = value != 0;
+            else
+                adjustScrollBar = value + extent >= maximum;
+        }
+
+        //  Reset the "value" so we can reposition the viewport and
+        //  distinguish between a user scroll and a program scroll.
+        //  (ie. valueChanged will be false on a program scroll)
+
+        if (adjustScrollBar && viewportPosition == END)
+        {
+            //  Scroll the viewport to the end.
+            scrollBar.removeAdjustmentListener( this );
+            value = maximum - extent;
+            scrollBar.setValue( value );
+            scrollBar.addAdjustmentListener( this );
+        }
+
+        if (adjustScrollBar && viewportPosition == START)
+        {
+            //  Keep the viewport at the same relative viewportPosition
+            scrollBar.removeAdjustmentListener( this );
+            value = value + maximum - previousMaximum;
+            scrollBar.setValue( value );
+            scrollBar.addAdjustmentListener( this );
+        }
+
+        previousValue = value;
+        previousMaximum = maximum;
+    }
+}