diff --git a/src/main/java/forge/gui/DialogMigrateProfile.java b/src/main/java/forge/gui/DialogMigrateProfile.java index c951f1ad623..57ee6f04fba 100644 --- a/src/main/java/forge/gui/DialogMigrateProfile.java +++ b/src/main/java/forge/gui/DialogMigrateProfile.java @@ -46,7 +46,6 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; -import javax.swing.text.DefaultCaret; import net.miginfocom.swing.MigLayout; @@ -63,6 +62,7 @@ import forge.gui.toolbox.FOverlay; import forge.gui.toolbox.FPanel; import forge.gui.toolbox.FSkin; import forge.gui.toolbox.FTextField; +import forge.gui.toolbox.SmartScroller; import forge.properties.NewConstants; /** @@ -244,8 +244,8 @@ public class DialogMigrateProfile { JPanel southPanel = new JPanel(new MigLayout("ax center")); southPanel.setOpaque(false); - southPanel.add(_btnStart, "center, w pref+72!, h pref+12!"); - southPanel.add(btnCancel, "center, w pref+72!, h pref+12!"); + southPanel.add(_btnStart, "center, w pref+144!, h pref+12!"); + southPanel.add(btnCancel, "center, w pref+144!, h pref+12!, gap 72"); p.add(southPanel, "growx"); JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel(); @@ -379,11 +379,9 @@ public class DialogMigrateProfile { _operationLog.setWrapStyleWord(true); _operationLog.setLineWrap(true); _operationLog.setEditable(false); - // autoscroll to bottom when we append text - // 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); + // autoscroll when we set/add text unless the user has intentionally scrolled somewhere else JScrollPane scroller = new JScrollPane(_operationLog); + new SmartScroller(scroller); scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); _selectionPanel.add(scroller, "w 400:100%:100%, h 60:100%:100%"); @@ -455,6 +453,7 @@ public class DialogMigrateProfile { @Override public void addOp(OpType type, File src, File dest) { + // add to concurrent map _selections.get(type).getRight().put(src, dest); } }; @@ -654,10 +653,9 @@ public class DialogMigrateProfile { if (0 < totalOps) { 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(isOverwrite ? "O" : "Not o"); - log.append("verwriting existing files"); + log.append(isOverwrite ? "O" : "Not o").append("verwriting existing files"); // set the JTextArea text directly (no need to use invokeLater: setText is thread-safe) _operationLog.setText(log.toString()); @@ -783,7 +781,7 @@ public class DialogMigrateProfile { // append summary footer _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)); } catch (final Exception e) { _cancel = true; diff --git a/src/main/java/forge/gui/toolbox/SmartScroller.java b/src/main/java/forge/gui/toolbox/SmartScroller.java new file mode 100644 index 00000000000..bd254ba9a75 --- /dev/null +++ b/src/main/java/forge/gui/toolbox/SmartScroller.java @@ -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; + } +}