package org.multibit.viewsystem.swing.view.components; import org.multibit.Localiser; import org.multibit.controller.Controller; import org.multibit.utils.HtmlUtils; import javax.swing.*; import javax.swing.event.ChangeListener; import java.awt.*; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Hashtable; /** * <p>Factory to provide the following to UI:</p> * <ul> * <li>Produce a slider</li> * </ul> * * @since 0.0.1 */ public class FeeSlider { public static final long MINIMUM_FEE_PER_KB = 1000; // Slightly higher than the minimum relay fee (1000 sat per KB) as per Bitcoin Core 0.9 public static final long DEFAULT_FEE_PER_KB = 10000; // 0.1 mBTC per KB - a long used fee structure which works as of spam attacks of July 2015 public static final long MAXIMUM_FEE_PER_KB = 50000; // 0.5 mBTC per KB public static final BigDecimal NUMBER_OF_SATOSHI_IN_A_BITCOIN = new BigDecimal(100000000); public static final int SCALE = 8; /** * Resolution of a single tick of the slider, in satoshi */ public static final int RESOLUTION = 200; /** * Utilities have no public constructor */ private FeeSlider() { } /** * <p>Create a new slider to adjust transaction fee</p> * * @param changeListener The change listener to respond to adjustments * @param initialPosition The initial position to choose on the slider, in satoshi */ public static JSlider newFeeSlider(Controller controller, ChangeListener changeListener, long initialPosition) { // Resolution is RESOLUTION satoshis per tick int minimumPosition = (int) MINIMUM_FEE_PER_KB / RESOLUTION; int defaultPosition = (int) DEFAULT_FEE_PER_KB / RESOLUTION; int maximumPosition = (int) MAXIMUM_FEE_PER_KB / RESOLUTION; // Make sure feePerKB is normalised first so that it will be in range of the slider int currentPosition = (int) normaliseFeePerKB(initialPosition) / RESOLUTION; JSlider feePerKBSlider = new JSlider(minimumPosition, maximumPosition, currentPosition); feePerKBSlider.setMajorTickSpacing(10); feePerKBSlider.setPaintTicks(true); feePerKBSlider.setPreferredSize(new Dimension(400, 120)); feePerKBSlider.setOpaque(false); feePerKBSlider.setBorder(BorderFactory.createEmptyBorder()); feePerKBSlider.setFont(FontSizer.INSTANCE.getAdjustedDefaultFont()); Localiser localiser = controller.getLocaliser(); // Create the label table Hashtable<Integer, JComponent> labelTable = new Hashtable<>(); JLabel lowerLabel = new JLabel(localiser.getString("sliders.lower")); lowerLabel.setOpaque(false); lowerLabel.setFont(FontSizer.INSTANCE.getAdjustedDefaultFont()); JLabel defaultLabel = newDefaultNote(localiser); defaultLabel.setOpaque(false); defaultLabel.setFont(FontSizer.INSTANCE.getAdjustedDefaultFont()); JLabel upperLabel = new JLabel(localiser.getString("sliders.higher")); upperLabel.setOpaque(false); upperLabel.setFont(FontSizer.INSTANCE.getAdjustedDefaultFont()); labelTable.put(minimumPosition, lowerLabel); labelTable.put(defaultPosition, defaultLabel); labelTable.put(maximumPosition, upperLabel); feePerKBSlider.setLabelTable(labelTable); feePerKBSlider.setPaintLabels(true); feePerKBSlider.addChangeListener(changeListener); return feePerKBSlider; } /** * Parse and normalise the feePerKB so that it is always between the minimum and maximum values * * @param rawFeePerKB the raw value of feePerKB, as String. From user preferences so may not be a number * @return the normalised feePerKB, as a long/ satoshi */ public static long parseAndNormaliseFeePerKB(String rawFeePerKB) { long feeValue; if (rawFeePerKB == null) { feeValue = FeeSlider.DEFAULT_FEE_PER_KB; } else { try { feeValue = Long.parseLong(rawFeePerKB); } catch (NumberFormatException nfe) { // Value in multibit.properties was not a number - use the default value feeValue = FeeSlider.DEFAULT_FEE_PER_KB; } } return normaliseFeePerKB(feeValue); } /** * Normalise the feePerKB so that it is always between the minimum and maximum values * * @param rawFeePerKB the raw value of feePerKB, as long/ satoshi * @return the normalised feePerKB, as a long/ satoshi */ public static long normaliseFeePerKB(long rawFeePerKB) { if (rawFeePerKB == 0) { return DEFAULT_FEE_PER_KB; } if (rawFeePerKB < MINIMUM_FEE_PER_KB) { return MINIMUM_FEE_PER_KB; } if (rawFeePerKB > MAXIMUM_FEE_PER_KB) { return MAXIMUM_FEE_PER_KB; } // Ok as is return rawFeePerKB; } /** * @return A new "default" note for use on the Fee slider */ private static JLabel newDefaultNote(Localiser localiser) { // Wrap in HTML to ensure LTR/RTL and line breaks are respected String[] lines = new String[2]; lines[0] = "\u007c"; // ASCII vertical line // 25B2 =up black triangle lines[1] = localiser.getString("sliders.default"); JLabel label = new JLabel(HtmlUtils.localiseCenteredWithLineBreaks(lines)); label.setHorizontalAlignment(SwingConstants.CENTER); return label; } public static String convertSatoshiToString(long satoshi) { BigDecimal satoshiBigDecimal = new BigDecimal(satoshi); return satoshiBigDecimal.divide(FeeSlider.NUMBER_OF_SATOSHI_IN_A_BITCOIN, FeeSlider.SCALE, RoundingMode.HALF_EVEN).toPlainString(); } }