/* * 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.util; import com.google.common.base.Charsets; import com.googlecode.jcsv.CSVStrategy; import com.googlecode.jcsv.writer.CSVEntryConverter; import com.googlecode.jcsv.writer.CSVWriter; import com.googlecode.jcsv.writer.internal.CSVWriterBuilder; import io.bitsquare.app.DevFlags; import io.bitsquare.common.util.Utilities; import io.bitsquare.gui.main.overlays.popups.Popup; import io.bitsquare.locale.BSResources; import io.bitsquare.locale.CurrencyUtil; import io.bitsquare.locale.TradeCurrency; import io.bitsquare.payment.PaymentAccount; import io.bitsquare.payment.PaymentMethod; import io.bitsquare.storage.Storage; import io.bitsquare.user.Preferences; import io.bitsquare.user.User; import javafx.collections.ObservableList; import javafx.geometry.Orientation; import javafx.scene.Node; import javafx.scene.control.ScrollBar; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.util.StringConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; public class GUIUtil { private static final Logger log = LoggerFactory.getLogger(GUIUtil.class); public final static String SHOW_ALL_FLAG = "SHOW_ALL_FLAG"; public final static String EDIT_FLAG = "EDIT_FLAG"; public static double getScrollbarWidth(Node scrollablePane) { Node node = scrollablePane.lookup(".scroll-bar"); if (node instanceof ScrollBar) { final ScrollBar bar = (ScrollBar) node; if (bar.getOrientation().equals(Orientation.VERTICAL)) return bar.getWidth(); } return 0; } public static void showFeeInfoBeforeExecute(Runnable runnable) { String key = "miningFeeInfo"; if (!DevFlags.DEV_MODE && Preferences.INSTANCE.showAgain(key)) { new Popup<>().information("Please be sure that the mining fee used at your external wallet is " + "sufficiently high so that the funding transaction will be accepted by the miners.\n" + "Otherwise the trade transactions cannot be confirmed and a trade would end up in a dispute.\n\n" + "You can check out the currently recommended fees at: https://bitcoinfees.21.co") .dontShowAgainId(key, Preferences.INSTANCE) .onClose(runnable::run) .closeButtonText("I understand") .show(); } else { runnable.run(); } } public static void exportAccounts(ArrayList<PaymentAccount> accounts, String fileName, Preferences preferences, Stage stage) { if (!accounts.isEmpty()) { String directory = getDirectoryFormChooser(preferences, stage); Storage<ArrayList<PaymentAccount>> paymentAccountsStorage = new Storage<>(new File(directory)); paymentAccountsStorage.initAndGetPersisted(accounts, fileName); paymentAccountsStorage.queueUpForSave(); new Popup<>().feedback("Trading accounts saved to path:\n" + Paths.get(directory, fileName).toAbsolutePath()).show(); } else { new Popup<>().warning("You don't have trading accounts set up for exporting.").show(); } } public static void importAccounts(User user, String fileName, Preferences preferences, Stage stage) { FileChooser fileChooser = new FileChooser(); fileChooser.setInitialDirectory(new File(preferences.getDefaultPath())); fileChooser.setTitle("Select path to " + fileName); File file = fileChooser.showOpenDialog(stage.getOwner()); if (file != null) { String path = file.getAbsolutePath(); if (Paths.get(path).getFileName().toString().equals(fileName)) { String directory = Paths.get(path).getParent().toString(); preferences.setDefaultPath(directory); Storage<ArrayList<PaymentAccount>> paymentAccountsStorage = new Storage<>(new File(directory)); ArrayList<PaymentAccount> persisted = paymentAccountsStorage.initAndGetPersistedWithFileName(fileName); if (persisted != null) { final StringBuilder msg = new StringBuilder(); persisted.stream().forEach(paymentAccount -> { final String id = paymentAccount.getId(); if (user.getPaymentAccount(id) == null) { user.addPaymentAccount(paymentAccount); msg.append("Trading account with id ").append(id).append("\n"); } else { msg.append("We did not import trading account with id ").append(id).append(" because it exists already.\n"); } }); new Popup<>().feedback("Trading account imported from path:\n" + path + "\n\nImported accounts:\n" + msg).show(); } else { new Popup<>().warning("No exported trading accounts has been found at path: " + path + ".\n" + "File name is " + fileName + ".").show(); } } else { new Popup<>().warning("The selected file is not the expected file for import. The expected file name is: " + fileName + ".").show(); } } } public static <T> void exportCSV(String fileName, CSVEntryConverter<T> headerConverter, CSVEntryConverter<T> contentConverter, T emptyItem, List<T> list, Stage stage) { FileChooser fileChooser = new FileChooser(); fileChooser.setInitialFileName(fileName); File file = fileChooser.showSaveDialog(stage); try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(file, false), Charsets.UTF_8)) { CSVWriter<T> headerWriter = new CSVWriterBuilder<T>(outputStreamWriter) .strategy(CSVStrategy.UK_DEFAULT) .entryConverter(headerConverter) .build(); headerWriter.write(emptyItem); CSVWriter<T> contentWriter = new CSVWriterBuilder<T>(outputStreamWriter) .strategy(CSVStrategy.UK_DEFAULT) .entryConverter(contentConverter) .build(); contentWriter.writeAll(list); } catch (RuntimeException | IOException e) { e.printStackTrace(); log.error(e.getMessage()); new Popup().error("Exporting to CSV failed because of an error.\n" + "Error = " + e.getMessage()); } } public static String getDirectoryFormChooser(Preferences preferences, Stage stage) { DirectoryChooser directoryChooser = new DirectoryChooser(); directoryChooser.setInitialDirectory(new File(preferences.getDefaultPath())); directoryChooser.setTitle("Select export path"); File dir = directoryChooser.showDialog(stage); if (dir != null) { String directory = dir.getAbsolutePath(); preferences.setDefaultPath(directory); return directory; } else { return ""; } } public static StringConverter<CurrencyListItem> getCurrencyListItemConverter(String postFix, Preferences preferences) { return new StringConverter<CurrencyListItem>() { @Override public String toString(CurrencyListItem item) { TradeCurrency tradeCurrency = item.tradeCurrency; String code = tradeCurrency.getCode(); if (code.equals(GUIUtil.SHOW_ALL_FLAG)) return "▶ Show all"; else if (code.equals(GUIUtil.EDIT_FLAG)) return "▼ Edit currency list"; else { String displayString = CurrencyUtil.getNameByCode(code) + " (" + code + ")"; if (preferences.getSortMarketCurrenciesNumerically()) displayString += " - " + item.numTrades + " " + postFix; return tradeCurrency.getDisplayPrefix() + displayString; } } @Override public CurrencyListItem fromString(String s) { return null; } }; } public static StringConverter<TradeCurrency> getTradeCurrencyConverter() { return new StringConverter<TradeCurrency>() { @Override public String toString(TradeCurrency tradeCurrency) { String code = tradeCurrency.getCode(); final String displayString = CurrencyUtil.getNameAndCode(code); // http://boschista.deviantart.com/journal/Cool-ASCII-Symbols-214218618 if (code.equals(GUIUtil.SHOW_ALL_FLAG)) return "▶ Show all"; else if (code.equals(GUIUtil.EDIT_FLAG)) return "▼ Edit currency list"; return tradeCurrency.getDisplayPrefix() + displayString; } @Override public TradeCurrency fromString(String s) { return null; } }; } public static void fillCurrencyListItems(List<TradeCurrency> tradeCurrencyList, ObservableList<CurrencyListItem> currencyListItems, @Nullable CurrencyListItem showAllCurrencyListItem, Preferences preferences) { Set<TradeCurrency> tradeCurrencySet = new HashSet<>(); Map<String, Integer> tradesPerCurrencyMap = new HashMap<>(); tradeCurrencyList.stream().forEach(tradeCurrency -> { tradeCurrencySet.add(tradeCurrency); String code = tradeCurrency.getCode(); if (tradesPerCurrencyMap.containsKey(code)) tradesPerCurrencyMap.put(code, tradesPerCurrencyMap.get(code) + 1); else tradesPerCurrencyMap.put(code, 1); }); List<CurrencyListItem> list = tradeCurrencySet.stream() .filter(e -> CurrencyUtil.isFiatCurrency(e.getCode())) .map(e -> new CurrencyListItem(e, tradesPerCurrencyMap.get(e.getCode()))) .collect(Collectors.toList()); List<CurrencyListItem> cryptoList = tradeCurrencySet.stream() .filter(e -> CurrencyUtil.isCryptoCurrency(e.getCode())) .map(e -> new CurrencyListItem(e, tradesPerCurrencyMap.get(e.getCode()))) .collect(Collectors.toList()); if (preferences.getSortMarketCurrenciesNumerically()) { list.sort((o1, o2) -> new Integer(o2.numTrades).compareTo(o1.numTrades)); cryptoList.sort((o1, o2) -> new Integer(o2.numTrades).compareTo(o1.numTrades)); } else { list.sort((o1, o2) -> o1.tradeCurrency.compareTo(o2.tradeCurrency)); cryptoList.sort((o1, o2) -> o1.tradeCurrency.compareTo(o2.tradeCurrency)); } list.addAll(cryptoList); if (showAllCurrencyListItem != null) list.add(0, showAllCurrencyListItem); currencyListItems.setAll(list); } public static void openWebPage(String target) { String key = "warnOpenURLWhenTorEnabled"; final Preferences preferences = Preferences.INSTANCE; if (preferences.showAgain(key)) { new Popup<>().information("You are going to open a web page " + "in your system web browser.\n" + "Do you want to open the web page now?\n\n" + "If you are not using the \"Tor Browser\" as your default system web browser you " + "will connect to the web page in clear net.\n\n" + "URL: \"" + target) .actionButtonText("Open the web page and don't ask again") .onAction(() -> { preferences.dontShowAgain(key, true); doOpenWebPage(target); }) .closeButtonText("Copy URL and cancel") .onClose(() -> Utilities.copyToClipboard(target)) .show(); } else { doOpenWebPage(target); } } private static void doOpenWebPage(String target) { try { Utilities.openURI(new URI(target)); } catch (Exception e) { e.printStackTrace(); log.error(e.getMessage()); } } public static void openMail(String to, String subject, String body) { try { subject = URLEncoder.encode(subject, "UTF-8").replace("+", "%20"); body = URLEncoder.encode(body, "UTF-8").replace("+", "%20"); Utilities.openURI(new URI("mailto:" + to + "?subject=" + subject + "&body=" + body)); } catch (IOException | URISyntaxException e) { log.error("openMail failed " + e.getMessage()); e.printStackTrace(); } } public static <T> T getParentOfType(Node node, Class<T> t) { Node parent = node.getParent(); while (parent != null) { if (parent.getClass().isAssignableFrom(t)) { break; } else { parent = parent.getParent(); } } return parent != null ? (T) parent : null; } public static void showClearXchangeWarning(PaymentMethod paymentMethod, Preferences preferences) { String key = "confirmClearXchangeRequirements"; final String cxc = BSResources.get(paymentMethod.getId()); new Popup().information("Your selected payment method is " + cxc + ". Please be sure that you fulfill the requirements for the usage of " + cxc + ".\n\n" + "1. You need to have your " + cxc + " account verified at their platform " + "before starting a trade or creating an offer.\n\n" + "2. You need to have a bank account at one of the following member banks:\n" + " ● Bank of America\n" + " ● Capital One P2P Payments\n" + " ● Chase QuickPay\n" + " ● FirstBank Person to Person Transfers\n" + " ● Frost Send Money\n" + " ● U.S. Bank Send Money\n" + " ● Wells Fargo SurePay\n\n" + "Please use " + cxc + " only if you fulfill those requirements, " + "otherwise it is very likely that the " + cxc + " transfer fails and the trade ends up in a dispute.\n" + "If you have not fulfilled the above requirements you would lose your security deposit in such a case.") .width(900) .closeButtonText("I confirm") .dontShowAgainId(key, preferences) .show(); } }