/* * This file is part of Bitsquare. * * Bitsquare is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * Bitsquare 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 Affero General Public * License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. */ package io.bitsquare.gui.main.settings.preferences; import io.bitsquare.common.UserThread; import io.bitsquare.common.util.Tuple2; import io.bitsquare.gui.common.model.Activatable; import io.bitsquare.gui.common.view.ActivatableViewAndModel; import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.components.InputTextField; import io.bitsquare.gui.components.TitledGroupBg; import io.bitsquare.gui.main.overlays.popups.Popup; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.ImageUtil; import io.bitsquare.gui.util.Layout; import io.bitsquare.locale.*; import io.bitsquare.user.BlockChainExplorer; import io.bitsquare.user.Preferences; import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.geometry.VPos; import javafx.scene.control.*; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.util.Callback; import javafx.util.StringConverter; import org.jetbrains.annotations.NotNull; import javax.inject.Inject; import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static io.bitsquare.gui.util.FormBuilder.*; @FxmlView public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatable> { // not supported yet //private ComboBox<String> btcDenominationComboBox; private ComboBox<BlockChainExplorer> blockChainExplorerComboBox; // private ComboBox<String> userLanguageComboBox; private ComboBox<TradeCurrency> preferredTradeCurrencyComboBox; private CheckBox useAnimationsCheckBox, autoSelectArbitratorsCheckBox, showOwnOffersInOfferBook, sortMarketCurrenciesNumericallyCheckBox/*, useStickyMarketPriceCheckBox*/; private int gridRow = 0; private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField; private ChangeListener<Boolean> transactionFeeFocusedListener; private final Preferences preferences; private BSFormatter formatter; private ListView<FiatCurrency> fiatCurrenciesListView; private ComboBox<FiatCurrency> fiatCurrenciesComboBox; private ListView<CryptoCurrency> cryptoCurrenciesListView; private ComboBox<CryptoCurrency> cryptoCurrenciesComboBox; private Button resetDontShowAgainButton; // private ListChangeListener<TradeCurrency> displayCurrenciesListChangeListener; final ObservableList<String> btcDenominations = FXCollections.observableArrayList(Preferences.getBtcDenominations()); final ObservableList<BlockChainExplorer> blockExplorers; final ObservableList<String> languageCodes; public final ObservableList<FiatCurrency> fiatCurrencies; public final ObservableList<FiatCurrency> allFiatCurrencies; public final ObservableList<CryptoCurrency> cryptoCurrencies; public final ObservableList<CryptoCurrency> allCryptoCurrencies; public final ObservableList<TradeCurrency> tradeCurrencies; private InputTextField deviationInputTextField; private ChangeListener<String> deviationListener, ignoreTradersListListener; private ChangeListener<Boolean> deviationFocusedListener; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, initialisation /////////////////////////////////////////////////////////////////////////////////////////// @Inject public PreferencesView(Preferences preferences, BSFormatter formatter) { super(); this.preferences = preferences; this.formatter = formatter; blockExplorers = FXCollections.observableArrayList(preferences.getBlockChainExplorers()); languageCodes = FXCollections.observableArrayList(LanguageUtil.getAllLanguageCodes()); fiatCurrencies = preferences.getFiatCurrenciesAsObservable(); cryptoCurrencies = preferences.getCryptoCurrenciesAsObservable(); tradeCurrencies = preferences.getTradeCurrenciesAsObservable(); allFiatCurrencies = FXCollections.observableArrayList(CurrencyUtil.getAllSortedFiatCurrencies()); allCryptoCurrencies = FXCollections.observableArrayList(CurrencyUtil.getAllSortedCryptoCurrencies()); allFiatCurrencies.removeAll(fiatCurrencies); allCryptoCurrencies.removeAll(cryptoCurrencies); } @Override public void initialize() { initializeDisplayCurrencies(); initializeOtherOptions(); initializeDisplayOptions(); } @Override protected void activate() { activateDisplayCurrencies(); activateOtherOptions(); activateDisplayPreferences(); } @Override protected void deactivate() { deactivateDisplayCurrencies(); deactivateOtherOptions(); deactivateDisplayPreferences(); } /////////////////////////////////////////////////////////////////////////////////////////// // Initialize /////////////////////////////////////////////////////////////////////////////////////////// private void initializeDisplayCurrencies() { TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, 3, "Currencies in market price feed list"); GridPane.setColumnSpan(titledGroupBg, 4); preferredTradeCurrencyComboBox = addLabelComboBox(root, gridRow, "Preferred currency:", Layout.FIRST_ROW_DISTANCE).second; preferredTradeCurrencyComboBox.setConverter(new StringConverter<TradeCurrency>() { @Override public String toString(TradeCurrency tradeCurrency) { // http://boschista.deviantart.com/journal/Cool-ASCII-Symbols-214218618 return tradeCurrency.getDisplayPrefix() + tradeCurrency.getNameAndCode(); } @Override public TradeCurrency fromString(String s) { return null; } }); Tuple2<Label, ListView> fiatTuple = addLabelListView(root, ++gridRow, "Display national currencies:"); GridPane.setValignment(fiatTuple.first, VPos.TOP); fiatCurrenciesListView = fiatTuple.second; fiatCurrenciesListView.setMinHeight(2 * Layout.LIST_ROW_HEIGHT + 2); fiatCurrenciesListView.setMaxHeight(6 * Layout.LIST_ROW_HEIGHT + 2); Label placeholder = new Label("There are no national currencies selected"); placeholder.setWrapText(true); fiatCurrenciesListView.setPlaceholder(placeholder); fiatCurrenciesListView.setCellFactory(new Callback<ListView<FiatCurrency>, ListCell<FiatCurrency>>() { @Override public ListCell<FiatCurrency> call(ListView<FiatCurrency> list) { return new ListCell<FiatCurrency>() { final Label label = new Label(); final ImageView icon = ImageUtil.getImageViewById(ImageUtil.REMOVE_ICON); final Button removeButton = new Button("", icon); final AnchorPane pane = new AnchorPane(label, removeButton); { label.setLayoutY(5); removeButton.setId("icon-button"); AnchorPane.setRightAnchor(removeButton, 0d); } @Override public void updateItem(final FiatCurrency item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { label.setText(item.getNameAndCode()); removeButton.setOnAction(e -> { if (item.equals(preferences.getPreferredTradeCurrency())) { new Popup().warning("You cannot remove your selected preferred display currency").show(); } else { preferences.removeFiatCurrency(item); if (!allFiatCurrencies.contains(item)) allFiatCurrencies.add(item); } }); setGraphic(pane); } else { setGraphic(null); removeButton.setOnAction(null); } } }; } }); Tuple2<Label, ListView> cryptoCurrenciesTuple = addLabelListView(root, gridRow, "Display altcoins:"); GridPane.setValignment(cryptoCurrenciesTuple.first, VPos.TOP); GridPane.setMargin(cryptoCurrenciesTuple.first, new Insets(0, 0, 0, 20)); cryptoCurrenciesListView = cryptoCurrenciesTuple.second; GridPane.setColumnIndex(cryptoCurrenciesTuple.first, 2); GridPane.setColumnIndex(cryptoCurrenciesListView, 3); cryptoCurrenciesListView.setMinHeight(2 * Layout.LIST_ROW_HEIGHT + 2); cryptoCurrenciesListView.setMaxHeight(6 * Layout.LIST_ROW_HEIGHT + 2); placeholder = new Label("There are no altcoins selected"); placeholder.setWrapText(true); cryptoCurrenciesListView.setPlaceholder(placeholder); cryptoCurrenciesListView.setCellFactory(new Callback<ListView<CryptoCurrency>, ListCell<CryptoCurrency>>() { @Override public ListCell<CryptoCurrency> call(ListView<CryptoCurrency> list) { return new ListCell<CryptoCurrency>() { final Label label = new Label(); final ImageView icon = ImageUtil.getImageViewById(ImageUtil.REMOVE_ICON); final Button removeButton = new Button("", icon); final AnchorPane pane = new AnchorPane(label, removeButton); { label.setLayoutY(5); removeButton.setId("icon-button"); AnchorPane.setRightAnchor(removeButton, 0d); } @Override public void updateItem(final CryptoCurrency item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { label.setText(item.getNameAndCode()); removeButton.setOnAction(e -> { if (item.equals(preferences.getPreferredTradeCurrency())) { new Popup().warning("You cannot remove your selected preferred display currency").show(); } else { preferences.removeCryptoCurrency(item); if (!allCryptoCurrencies.contains(item)) allCryptoCurrencies.add(item); } }); setGraphic(pane); } else { setGraphic(null); removeButton.setOnAction(null); } } }; } }); fiatCurrenciesComboBox = addLabelComboBox(root, ++gridRow).second; fiatCurrenciesComboBox.setPromptText("Add national currency"); fiatCurrenciesComboBox.setConverter(new StringConverter<FiatCurrency>() { @Override public String toString(FiatCurrency tradeCurrency) { return tradeCurrency.getNameAndCode(); } @Override public FiatCurrency fromString(String s) { return null; } }); Tuple2<Label, ComboBox> labelComboBoxTuple2 = addLabelComboBox(root, gridRow); cryptoCurrenciesComboBox = labelComboBoxTuple2.second; GridPane.setColumnIndex(cryptoCurrenciesComboBox, 3); cryptoCurrenciesComboBox.setPromptText("Add altcoin"); cryptoCurrenciesComboBox.setConverter(new StringConverter<CryptoCurrency>() { @Override public String toString(CryptoCurrency tradeCurrency) { return tradeCurrency.getNameAndCode(); } @Override public CryptoCurrency fromString(String s) { return null; } }); } private void initializeOtherOptions() { TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 5, "General preferences", Layout.GROUP_DISTANCE); GridPane.setColumnSpan(titledGroupBg, 4); // userLanguageComboBox = addLabelComboBox(root, gridRow, "Language:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; // btcDenominationComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin denomination:").second; blockChainExplorerComboBox = addLabelComboBox(root, gridRow, "Bitcoin block explorer:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; deviationInputTextField = addLabelInputTextField(root, ++gridRow, "Max. deviation from market price:").second; autoSelectArbitratorsCheckBox = addLabelCheckBox(root, ++gridRow, "Auto select arbitrators:", "").second; deviationListener = (observable, oldValue, newValue) -> { try { double value = formatter.parsePercentStringToDouble(newValue); if (value <= 0.3) { preferences.setMaxPriceDistanceInPercent(value); } else { new Popup().warning("Values higher than 30 % are not allowed.").show(); UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatPercentagePrice(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS); } } catch (NumberFormatException t) { log.error("Exception at parseDouble deviation: " + t.toString()); UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatPercentagePrice(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS); } }; deviationFocusedListener = (observable1, oldValue1, newValue1) -> { if (oldValue1 && !newValue1) UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatPercentagePrice(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS); }; transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Withdrawal transaction fee (satoshi/byte):").second; transactionFeeFocusedListener = (o, oldValue, newValue) -> { if (oldValue && !newValue) { try { int val = Integer.parseInt(transactionFeeInputTextField.getText()); preferences.setNonTradeTxFeePerKB(val * 1000); } catch (NumberFormatException t) { new Popup().warning("Please enter integer numbers only.").show(); transactionFeeInputTextField.setText(getNonTradeTxFeePerKB()); } catch (Throwable t) { new Popup().warning("Your input was not accepted.\n" + t.getMessage()).show(); transactionFeeInputTextField.setText(getNonTradeTxFeePerKB()); } } }; ignoreTradersListInputTextField = addLabelInputTextField(root, ++gridRow, "Ignore traders with onion address (comma sep.):").second; ignoreTradersListListener = (observable, oldValue, newValue) -> preferences.setIgnoreTradersList(Arrays.asList(newValue.replace(" ", "").replace(":9999", "").replace(".onion", "").split(","))); } private void initializeDisplayOptions() { TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 4, "Display options", Layout.GROUP_DISTANCE); GridPane.setColumnSpan(titledGroupBg, 4); showOwnOffersInOfferBook = addLabelCheckBox(root, gridRow, "Show my own offers in offer book:", "", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; useAnimationsCheckBox = addLabelCheckBox(root, ++gridRow, "Use animations:", "").second; // useStickyMarketPriceCheckBox = addLabelCheckBox(root, ++gridRow, "Use sticky market price:", "").second; sortMarketCurrenciesNumericallyCheckBox = addLabelCheckBox(root, ++gridRow, "Sort market lists with no. of offers/trades:", "").second; resetDontShowAgainButton = addLabelButton(root, ++gridRow, "Reset all 'Don't show again' flags:", "Reset", 0).second; } /////////////////////////////////////////////////////////////////////////////////////////// // Activate /////////////////////////////////////////////////////////////////////////////////////////// private void activateDisplayCurrencies() { preferredTradeCurrencyComboBox.setItems(tradeCurrencies); preferredTradeCurrencyComboBox.getSelectionModel().select(preferences.getPreferredTradeCurrency()); preferredTradeCurrencyComboBox.setVisibleRowCount(25); preferredTradeCurrencyComboBox.setOnAction(e -> { TradeCurrency selectedItem = preferredTradeCurrencyComboBox.getSelectionModel().getSelectedItem(); if (selectedItem != null) preferences.setPreferredTradeCurrency(selectedItem); }); fiatCurrenciesComboBox.setItems(allFiatCurrencies); fiatCurrenciesListView.setItems(fiatCurrencies); fiatCurrenciesComboBox.setOnAction(e -> { FiatCurrency selectedItem = fiatCurrenciesComboBox.getSelectionModel().getSelectedItem(); if (selectedItem != null) { preferences.addFiatCurrency(selectedItem); if (allFiatCurrencies.contains(selectedItem)) { UserThread.execute(() -> { fiatCurrenciesComboBox.getSelectionModel().clearSelection(); allFiatCurrencies.remove(selectedItem); }); } } }); cryptoCurrenciesComboBox.setItems(allCryptoCurrencies); cryptoCurrenciesListView.setItems(cryptoCurrencies); cryptoCurrenciesComboBox.setOnAction(e -> { CryptoCurrency selectedItem = cryptoCurrenciesComboBox.getSelectionModel().getSelectedItem(); if (selectedItem != null) { preferences.addCryptoCurrency(selectedItem); if (allCryptoCurrencies.contains(selectedItem)) { UserThread.execute(() -> { cryptoCurrenciesComboBox.getSelectionModel().clearSelection(); allCryptoCurrencies.remove(selectedItem); }); } } }); } private void activateOtherOptions() { transactionFeeInputTextField.setText(getNonTradeTxFeePerKB()); ignoreTradersListInputTextField.setText(preferences.getIgnoreTradersList().stream().collect(Collectors.joining(", "))); /* btcDenominationComboBox.setDisable(true); btcDenominationComboBox.setItems(btcDenominations); btcDenominationComboBox.getSelectionModel().select(getBtcDenomination()); btcDenominationComboBox.setOnAction(e -> onSelectBtcDenomination(btcDenominationComboBox.getSelectionModel().getSelectedItem()));*/ /* userLanguageComboBox.setItems(languageCodes); userLanguageComboBox.getSelectionModel().select(preferences.getPreferredLocale().getLanguage()); userLanguageComboBox.setConverter(new StringConverter<String>() { @Override public String toString(String code) { return LanguageUtil.getDisplayName(code); } @Override public String fromString(String string) { return null; } }); userLanguageComboBox.setOnAction(e -> { String code = userLanguageComboBox.getSelectionModel().getSelectedItem(); preferences.setPreferredLocale(new Locale(code, preferences.getPreferredLocale().getCountry())); });*/ blockChainExplorerComboBox.setItems(blockExplorers); blockChainExplorerComboBox.getSelectionModel().select(preferences.getBlockChainExplorer()); blockChainExplorerComboBox.setConverter(new StringConverter<BlockChainExplorer>() { @Override public String toString(BlockChainExplorer blockChainExplorer) { return blockChainExplorer.name; } @Override public BlockChainExplorer fromString(String string) { return null; } }); blockChainExplorerComboBox.setOnAction(e -> preferences.setBlockChainExplorer(blockChainExplorerComboBox.getSelectionModel().getSelectedItem())); deviationInputTextField.setText(formatter.formatPercentagePrice(preferences.getMaxPriceDistanceInPercent())); deviationInputTextField.textProperty().addListener(deviationListener); deviationInputTextField.focusedProperty().addListener(deviationFocusedListener); transactionFeeInputTextField.focusedProperty().addListener(transactionFeeFocusedListener); ignoreTradersListInputTextField.textProperty().addListener(ignoreTradersListListener); } @NotNull private String getNonTradeTxFeePerKB() { return String.valueOf(preferences.getNonTradeTxFeePerKB() / 1000); } private void activateDisplayPreferences() { showOwnOffersInOfferBook.setSelected(preferences.getShowOwnOffersInOfferBook()); showOwnOffersInOfferBook.setOnAction(e -> preferences.setShowOwnOffersInOfferBook(showOwnOffersInOfferBook.isSelected())); useAnimationsCheckBox.setSelected(preferences.getUseAnimations()); useAnimationsCheckBox.setOnAction(e -> preferences.setUseAnimations(useAnimationsCheckBox.isSelected())); // useStickyMarketPriceCheckBox.setSelected(preferences.getUseStickyMarketPrice()); // useStickyMarketPriceCheckBox.setOnAction(e -> preferences.setUseStickyMarketPrice(useStickyMarketPriceCheckBox.isSelected())); sortMarketCurrenciesNumericallyCheckBox.setSelected(preferences.getSortMarketCurrenciesNumerically()); sortMarketCurrenciesNumericallyCheckBox.setOnAction(e -> preferences.setSortMarketCurrenciesNumerically(sortMarketCurrenciesNumericallyCheckBox.isSelected())); resetDontShowAgainButton.setOnAction(e -> preferences.resetDontShowAgainForType()); autoSelectArbitratorsCheckBox.setSelected(preferences.getAutoSelectArbitrators()); autoSelectArbitratorsCheckBox.setOnAction(e -> preferences.setAutoSelectArbitrators(autoSelectArbitratorsCheckBox.isSelected())); } /////////////////////////////////////////////////////////////////////////////////////////// // Deactivate /////////////////////////////////////////////////////////////////////////////////////////// private void deactivateDisplayCurrencies() { preferredTradeCurrencyComboBox.setOnAction(null); } private void deactivateOtherOptions() { //btcDenominationComboBox.setOnAction(null); // userLanguageComboBox.setOnAction(null); blockChainExplorerComboBox.setOnAction(null); deviationInputTextField.textProperty().removeListener(deviationListener); deviationInputTextField.focusedProperty().removeListener(deviationFocusedListener); transactionFeeInputTextField.focusedProperty().removeListener(transactionFeeFocusedListener); ignoreTradersListInputTextField.textProperty().removeListener(ignoreTradersListListener); } private void deactivateDisplayPreferences() { useAnimationsCheckBox.setOnAction(null); // useStickyMarketPriceCheckBox.setOnAction(null); sortMarketCurrenciesNumericallyCheckBox.setOnAction(null); showOwnOffersInOfferBook.setOnAction(null); autoSelectArbitratorsCheckBox.setOnAction(null); resetDontShowAgainButton.setOnAction(null); } }