mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
don't autoscroll in import dialog if user has manually scrolled somewhere other than the bottom
This commit is contained in:
@@ -46,7 +46,6 @@ import javax.swing.event.ChangeEvent;
|
|||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
import javax.swing.event.DocumentListener;
|
import javax.swing.event.DocumentListener;
|
||||||
import javax.swing.text.DefaultCaret;
|
|
||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
@@ -63,6 +62,7 @@ import forge.gui.toolbox.FOverlay;
|
|||||||
import forge.gui.toolbox.FPanel;
|
import forge.gui.toolbox.FPanel;
|
||||||
import forge.gui.toolbox.FSkin;
|
import forge.gui.toolbox.FSkin;
|
||||||
import forge.gui.toolbox.FTextField;
|
import forge.gui.toolbox.FTextField;
|
||||||
|
import forge.gui.toolbox.SmartScroller;
|
||||||
import forge.properties.NewConstants;
|
import forge.properties.NewConstants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -244,8 +244,8 @@ public class DialogMigrateProfile {
|
|||||||
|
|
||||||
JPanel southPanel = new JPanel(new MigLayout("ax center"));
|
JPanel southPanel = new JPanel(new MigLayout("ax center"));
|
||||||
southPanel.setOpaque(false);
|
southPanel.setOpaque(false);
|
||||||
southPanel.add(_btnStart, "center, w pref+72!, h pref+12!");
|
southPanel.add(_btnStart, "center, w pref+144!, h pref+12!");
|
||||||
southPanel.add(btnCancel, "center, w pref+72!, h pref+12!");
|
southPanel.add(btnCancel, "center, w pref+144!, h pref+12!, gap 72");
|
||||||
p.add(southPanel, "growx");
|
p.add(southPanel, "growx");
|
||||||
|
|
||||||
JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel();
|
JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel();
|
||||||
@@ -379,11 +379,9 @@ public class DialogMigrateProfile {
|
|||||||
_operationLog.setWrapStyleWord(true);
|
_operationLog.setWrapStyleWord(true);
|
||||||
_operationLog.setLineWrap(true);
|
_operationLog.setLineWrap(true);
|
||||||
_operationLog.setEditable(false);
|
_operationLog.setEditable(false);
|
||||||
// autoscroll to bottom when we append text
|
// autoscroll when we set/add text unless the user has intentionally scrolled somewhere else
|
||||||
// it would be nice if we only autoscrolled when the caret is at the bottom, though
|
|
||||||
DefaultCaret caret = (DefaultCaret)_operationLog.getCaret();
|
|
||||||
caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
|
|
||||||
JScrollPane scroller = new JScrollPane(_operationLog);
|
JScrollPane scroller = new JScrollPane(_operationLog);
|
||||||
|
new SmartScroller(scroller);
|
||||||
scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||||
_selectionPanel.add(scroller, "w 400:100%:100%, h 60:100%:100%");
|
_selectionPanel.add(scroller, "w 400:100%:100%, h 60:100%:100%");
|
||||||
|
|
||||||
@@ -455,6 +453,7 @@ public class DialogMigrateProfile {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addOp(OpType type, File src, File dest) {
|
public void addOp(OpType type, File src, File dest) {
|
||||||
|
// add to concurrent map
|
||||||
_selections.get(type).getRight().put(src, dest);
|
_selections.get(type).getRight().put(src, dest);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -654,10 +653,9 @@ public class DialogMigrateProfile {
|
|||||||
if (0 < totalOps) {
|
if (0 < totalOps) {
|
||||||
log.append("\n");
|
log.append("\n");
|
||||||
}
|
}
|
||||||
log.append(isMove ? "Moving" : "Copying");
|
log.append("Prepared to ").append(isMove ? "move" : "copy");
|
||||||
log.append(" ").append(totalOps).append(" files\n");
|
log.append(" ").append(totalOps).append(" files\n");
|
||||||
log.append(isOverwrite ? "O" : "Not o");
|
log.append(isOverwrite ? "O" : "Not o").append("verwriting existing files");
|
||||||
log.append("verwriting existing files");
|
|
||||||
|
|
||||||
// set the JTextArea text directly (no need to use invokeLater: setText is thread-safe)
|
// set the JTextArea text directly (no need to use invokeLater: setText is thread-safe)
|
||||||
_operationLog.setText(log.toString());
|
_operationLog.setText(log.toString());
|
||||||
@@ -783,7 +781,7 @@ public class DialogMigrateProfile {
|
|||||||
|
|
||||||
// append summary footer
|
// append summary footer
|
||||||
_operationLog.append(opLogBuf.toString());
|
_operationLog.append(opLogBuf.toString());
|
||||||
_operationLog.append(String.format("\nImport complete. %d files %s, %d errors",
|
_operationLog.append(String.format("\nImport complete: %d files %s, %d errors",
|
||||||
numSucceeded, _move ? "moved" : "copied", numFailed));
|
numSucceeded, _move ? "moved" : "copied", numFailed));
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
_cancel = true;
|
_cancel = true;
|
||||||
|
|||||||
160
src/main/java/forge/gui/toolbox/SmartScroller.java
Normal file
160
src/main/java/forge/gui/toolbox/SmartScroller.java
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
|
||||||
|
// based on code from http://tips4java.wordpress.com/2013/03/03/smart-scrolling/
|
||||||
|
package forge.gui.toolbox;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
@Override 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user