/******************************************************************************* * This file is part of logisim-evolution. * * logisim-evolution is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * logisim-evolution is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with logisim-evolution. If not, see <http://www.gnu.org/licenses/>. * * Original code by Carl Burch (http://www.cburch.com), 2011. * Subsequent modifications by : * + Haute École Spécialisée Bernoise * http://www.bfh.ch * + Haute École du paysage, d'ingénierie et d'architecture de Genève * http://hepia.hesge.ch/ * + Haute École d'Ingénierie et de Gestion du Canton de Vaud * http://www.heig-vd.ch/ * The project is currently maintained by : * + REDS Institute - HEIG-VD * Yverdon-les-Bains, Switzerland * http://reds.heig-vd.ch *******************************************************************************/ package com.cburch.logisim.util; import java.awt.Component; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import javax.swing.BoundedRangeModel; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.text.DefaultCaret; import javax.swing.text.JTextComponent; /** * 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. * * http://www.camick.com/java/source/SmartScroller.java */ 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; } }