diff --git a/core/src/main/java/io/bitsquare/btc/pricefeed/PriceFeed.java b/core/src/main/java/io/bitsquare/btc/pricefeed/PriceFeed.java index 613f808117..5efb81bc81 100644 --- a/core/src/main/java/io/bitsquare/btc/pricefeed/PriceFeed.java +++ b/core/src/main/java/io/bitsquare/btc/pricefeed/PriceFeed.java @@ -8,6 +8,7 @@ import io.bitsquare.app.Log; import io.bitsquare.btc.pricefeed.providers.BitcoinAveragePriceProvider; import io.bitsquare.btc.pricefeed.providers.PoloniexPriceProvider; import io.bitsquare.btc.pricefeed.providers.PriceProvider; +import io.bitsquare.common.Timer; import io.bitsquare.common.UserThread; import io.bitsquare.common.handlers.FaultHandler; import io.bitsquare.locale.CurrencyUtil; @@ -24,6 +25,9 @@ import java.util.function.Consumer; public class PriceFeed { private static final Logger log = LoggerFactory.getLogger(PriceFeed.class); + private static final long MIN_PERIOD_BETWEEN_CALLS = 5000; + + /////////////////////////////////////////////////////////////////////////////////////////// // Enum /////////////////////////////////////////////////////////////////////////////////////////// @@ -40,9 +44,8 @@ public class PriceFeed { } } - private static final long PERIOD_FIAT_SEC = 60; + private static final long PERIOD_FIAT_SEC = 2 * 60; private static final long PERIOD_ALL_FIAT_SEC = 60 * 5; - private static final long PERIOD_CRYPTO_SEC = 60; private static final long PERIOD_ALL_CRYPTO_SEC = 60 * 5; private final Map cache = new HashMap<>(); @@ -55,6 +58,11 @@ public class PriceFeed { private final StringProperty currencyCodeProperty = new SimpleStringProperty(); private final ObjectProperty typeProperty = new SimpleObjectProperty<>(); private final IntegerProperty currenciesUpdateFlag = new SimpleIntegerProperty(0); + private long bitcoinAveragePriceProviderLastCallAllTs; + private long poloniexPriceProviderLastCallAllTs; + private long bitcoinAveragePriceProviderLastCallTs; + private Timer cryptoCurrenciesTime; + private Timer fiatCurrenciesTime; /////////////////////////////////////////////////////////////////////////////////////////// @@ -76,19 +84,18 @@ public class PriceFeed { requestAllPrices(fiatPriceProvider, () -> { applyPrice(); - UserThread.runPeriodically(() -> requestPrice(fiatPriceProvider), PERIOD_FIAT_SEC); + if (fiatCurrenciesTime == null) + fiatCurrenciesTime = UserThread.runPeriodically(() -> requestPrice(fiatPriceProvider), PERIOD_FIAT_SEC); }); requestAllPrices(cryptoCurrenciesPriceProvider, () -> { applyPrice(); - UserThread.runPeriodically(() -> requestAllPrices(cryptoCurrenciesPriceProvider, this::applyPrice), - PERIOD_CRYPTO_SEC); + if (cryptoCurrenciesTime == null) + cryptoCurrenciesTime = UserThread.runPeriodically(() -> requestAllPrices(cryptoCurrenciesPriceProvider, this::applyPrice), + PERIOD_ALL_CRYPTO_SEC); }); UserThread.runPeriodically(() -> requestAllPrices(fiatPriceProvider, this::applyPrice), PERIOD_ALL_FIAT_SEC); - UserThread.runPeriodically(() -> requestAllPrices(cryptoCurrenciesPriceProvider, this::applyPrice), PERIOD_ALL_CRYPTO_SEC); - - requestAllPrices(cryptoCurrenciesPriceProvider, this::applyPrice); } @Nullable @@ -110,14 +117,18 @@ public class PriceFeed { } public void setCurrencyCode(String currencyCode) { - this.currencyCode = currencyCode; - currencyCodeProperty.set(currencyCode); - applyPrice(); + if (this.currencyCode != currencyCode) { + this.currencyCode = currencyCode; + currencyCodeProperty.set(currencyCode); + applyPrice(); - if (CurrencyUtil.isFiatCurrency(currencyCode)) - requestPrice(fiatPriceProvider); - else - requestPrice(cryptoCurrenciesPriceProvider); + if (CurrencyUtil.isFiatCurrency(currencyCode)) { + requestPrice(fiatPriceProvider); + } else { + // Poloniex does not support calls for one currency just for all which is quite a bit of data + requestAllPrices(cryptoCurrenciesPriceProvider, this::applyPrice); + } + } } @@ -154,7 +165,6 @@ public class PriceFeed { if (priceConsumer != null && currencyCode != null && type != null) { if (cache.containsKey(currencyCode)) { MarketPrice marketPrice = cache.get(currencyCode); - //log.debug("applyPrice type=" + type); if (marketPrice != null) priceConsumer.accept(marketPrice.getPrice(type)); } else { @@ -168,39 +178,66 @@ public class PriceFeed { private void requestPrice(PriceProvider provider) { Log.traceCall(); - GetPriceRequest getPriceRequest = new GetPriceRequest(); - SettableFuture future = getPriceRequest.requestPrice(currencyCode, provider); - Futures.addCallback(future, new FutureCallback() { - public void onSuccess(MarketPrice marketPrice) { - UserThread.execute(() -> { - cache.put(marketPrice.currencyCode, marketPrice); - //log.debug("marketPrice updated " + marketPrice); - priceConsumer.accept(marketPrice.getPrice(type)); - }); - } + long now = System.currentTimeMillis(); + boolean allowed = false; + if (now - bitcoinAveragePriceProviderLastCallTs > MIN_PERIOD_BETWEEN_CALLS) { + bitcoinAveragePriceProviderLastCallTs = now; + allowed = true; + } - public void onFailure(@NotNull Throwable throwable) { - log.debug("Could not load marketPrice\n" + throwable.getMessage()); - } - }); + if (allowed) { + GetPriceRequest getPriceRequest = new GetPriceRequest(); + SettableFuture future = getPriceRequest.requestPrice(currencyCode, provider); + Futures.addCallback(future, new FutureCallback() { + public void onSuccess(MarketPrice marketPrice) { + UserThread.execute(() -> { + cache.put(marketPrice.currencyCode, marketPrice); + priceConsumer.accept(marketPrice.getPrice(type)); + }); + } + + public void onFailure(@NotNull Throwable throwable) { + log.debug("Could not load marketPrice\n" + throwable.getMessage()); + } + }); + } else { + log.debug("Ignore request. Too many attempt to call the API provider " + provider); + } } private void requestAllPrices(PriceProvider provider, @Nullable Runnable resultHandler) { Log.traceCall(); - GetPriceRequest getPriceRequest = new GetPriceRequest(); - SettableFuture> future = getPriceRequest.requestAllPrices(provider); - Futures.addCallback(future, new FutureCallback>() { - public void onSuccess(Map marketPriceMap) { - UserThread.execute(() -> { - cache.putAll(marketPriceMap); - if (resultHandler != null) - resultHandler.run(); - }); + long now = System.currentTimeMillis(); + boolean allowed = false; + if (provider instanceof BitcoinAveragePriceProvider) { + if (now - bitcoinAveragePriceProviderLastCallAllTs > MIN_PERIOD_BETWEEN_CALLS) { + bitcoinAveragePriceProviderLastCallAllTs = now; + allowed = true; } + } else if (provider instanceof PoloniexPriceProvider) { + if (now - poloniexPriceProviderLastCallAllTs > MIN_PERIOD_BETWEEN_CALLS) { + poloniexPriceProviderLastCallAllTs = now; + allowed = true; + } + } + if (allowed) { + GetPriceRequest getPriceRequest = new GetPriceRequest(); + SettableFuture> future = getPriceRequest.requestAllPrices(provider); + Futures.addCallback(future, new FutureCallback>() { + public void onSuccess(Map marketPriceMap) { + UserThread.execute(() -> { + cache.putAll(marketPriceMap); + if (resultHandler != null) + resultHandler.run(); + }); + } - public void onFailure(@NotNull Throwable throwable) { - UserThread.execute(() -> faultHandler.handleFault("Could not load marketPrices", throwable)); - } - }); + public void onFailure(@NotNull Throwable throwable) { + UserThread.execute(() -> faultHandler.handleFault("Could not load marketPrices", throwable)); + } + }); + } else { + log.debug("Ignore request. Too many attempt to call the API provider " + provider); + } } } diff --git a/gui/src/main/java/io/bitsquare/gui/bitsquare.css b/gui/src/main/java/io/bitsquare/gui/bitsquare.css index 4824305caf..cb8830549b 100644 --- a/gui/src/main/java/io/bitsquare/gui/bitsquare.css +++ b/gui/src/main/java/io/bitsquare/gui/bitsquare.css @@ -1003,6 +1003,7 @@ textfield */ #price-feed-combo { -fx-background-color: #555; -fx-text-fill: white; + -fx-alignment: center; } #invert-market-price { diff --git a/gui/src/main/java/io/bitsquare/gui/images.css b/gui/src/main/java/io/bitsquare/gui/images.css index acd9aa0d46..b890769697 100644 --- a/gui/src/main/java/io/bitsquare/gui/images.css +++ b/gui/src/main/java/io/bitsquare/gui/images.css @@ -212,6 +212,12 @@ -fx-image: url("../../../images/link.png"); } +#invert { + -fx-image: url("../../../images/invert.png"); +} + + + #avatar_1 { -fx-image: url("../../../images/avatars/avatar_1.png"); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainView.java b/gui/src/main/java/io/bitsquare/gui/main/MainView.java index 3a5f028235..e1f03deb19 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainView.java @@ -17,8 +17,6 @@ package io.bitsquare.gui.main; -import de.jensd.fx.fontawesome.AwesomeDude; -import de.jensd.fx.fontawesome.AwesomeIcon; import io.bitsquare.BitsquareException; import io.bitsquare.app.BitsquareApp; import io.bitsquare.btc.pricefeed.PriceFeed; @@ -266,6 +264,7 @@ public class MainView extends InitializableView { @Override protected void updateItem(PriceFeedComboBoxItem item, boolean empty) { super.updateItem(item, empty); + if (!empty && item != null) { textProperty().bind(item.displayStringProperty); } else { @@ -287,18 +286,19 @@ public class MainView extends InitializableView { buttonCell.setId("price-feed-combo"); priceComboBox.setButtonCell(buttonCell); - Label invertIcon = new Label(); - HBox.setMargin(invertIcon, new Insets(3, 0, 0, 0)); - invertIcon.setId("invert-market-price"); - invertIcon.setOpacity(0.8); - invertIcon.setOnMouseClicked(e -> { - model.preferences.flipUseInvertedMarketPrice(); - }); + + final ImageView invertIcon = new ImageView(); + invertIcon.setId("invert"); + final Button invertIconButton = new Button("", invertIcon); + invertIconButton.setPadding(new Insets(0, 0, 0, 0)); + invertIconButton.setFocusTraversable(false); + invertIconButton.setStyle("-fx-background-color: transparent;"); + //invertIconButton.setStyle("-fx-focus-color: transparent; -fx-border-radius: 0 2 2 0; -fx-border-color: #aaa; -fx-border-insets: 0 0 0 -1; -fx-border-style: solid solid solid none; -fx-padding: 1 0 1 0;"); + HBox.setMargin(invertIconButton, new Insets(2, 0, 0, 0)); + invertIconButton.setOnAction(e -> model.preferences.flipUseInvertedMarketPrice()); HBox hBox = new HBox(); - hBox.setSpacing(5); - AwesomeDude.setIcon(invertIcon, AwesomeIcon.RETWEET, "14.0"); - hBox.getChildren().setAll(priceComboBox, invertIcon); + hBox.getChildren().setAll(priceComboBox, invertIconButton); Label label = new Label(text); label.setId("nav-balance-label"); diff --git a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/DisplayAlertMessageWindow.java b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/DisplayAlertMessageWindow.java index 493ff5782e..a1a2041471 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/DisplayAlertMessageWindow.java +++ b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/DisplayAlertMessageWindow.java @@ -18,7 +18,6 @@ package io.bitsquare.gui.main.overlays.windows; import io.bitsquare.alert.Alert; -import io.bitsquare.common.util.Utilities; import io.bitsquare.gui.components.HyperlinkWithIcon; import io.bitsquare.gui.main.overlays.Overlay; import javafx.geometry.Insets; @@ -73,8 +72,7 @@ public class DisplayAlertMessageWindow extends Overlay Utilities.openWebPage(url)); + "Download:", url, url).second; hyperlinkWithIcon.setMaxWidth(550); } else { headLine = "Important information!"; diff --git a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/TacWindow.java b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/TacWindow.java index b3ea7c8313..59a7c57e0e 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/TacWindow.java +++ b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/TacWindow.java @@ -2,8 +2,6 @@ package io.bitsquare.gui.main.overlays.windows; import com.google.inject.Inject; import io.bitsquare.app.BitsquareApp; -import io.bitsquare.common.util.Utilities; -import io.bitsquare.gui.components.HyperlinkWithIcon; import io.bitsquare.gui.main.overlays.Overlay; import io.bitsquare.user.Preferences; import org.slf4j.Logger; @@ -53,9 +51,7 @@ public class TacWindow extends Overlay { @Override protected void addMessage() { super.addMessage(); - String url = "https://bitsquare.io/arbitration_system.pdf"; - HyperlinkWithIcon hyperlinkWithIcon = addHyperlinkWithIcon(gridPane, ++rowIndex, url, -8); - hyperlinkWithIcon.setOnAction(e -> Utilities.openWebPage(url)); + addHyperlinkWithIcon(gridPane, ++rowIndex, "Arbitration system", "https://bitsquare.io/arbitration_system.pdf", -6); } @Override diff --git a/gui/src/main/java/io/bitsquare/gui/main/settings/SettingsView.fxml b/gui/src/main/java/io/bitsquare/gui/main/settings/SettingsView.fxml index ff671ea972..de33ff7579 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/settings/SettingsView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/settings/SettingsView.fxml @@ -27,4 +27,5 @@ + diff --git a/gui/src/main/java/io/bitsquare/gui/main/settings/SettingsView.java b/gui/src/main/java/io/bitsquare/gui/main/settings/SettingsView.java index c56874acee..60066acc92 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/settings/SettingsView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/settings/SettingsView.java @@ -21,6 +21,7 @@ import io.bitsquare.gui.Navigation; import io.bitsquare.gui.common.model.Activatable; import io.bitsquare.gui.common.view.*; import io.bitsquare.gui.main.MainView; +import io.bitsquare.gui.main.settings.about.AboutView; import io.bitsquare.gui.main.settings.network.NetworkSettingsView; import io.bitsquare.gui.main.settings.preferences.PreferencesView; import javafx.beans.value.ChangeListener; @@ -33,7 +34,7 @@ import javax.inject.Inject; @FxmlView public class SettingsView extends ActivatableViewAndModel { @FXML - Tab preferencesTab, networkSettingsTab; + Tab preferencesTab, networkSettingsTab, aboutTab; private final ViewLoader viewLoader; private final Navigation navigation; private Navigation.Listener navigationListener; @@ -57,6 +58,8 @@ public class SettingsView extends ActivatableViewAndModel navigation.navigateTo(MainView.class, SettingsView.class, PreferencesView.class); else if (newValue == networkSettingsTab) navigation.navigateTo(MainView.class, SettingsView.class, NetworkSettingsView.class); + else if (newValue == aboutTab) + navigation.navigateTo(MainView.class, SettingsView.class, AboutView.class); }; } @@ -65,10 +68,13 @@ public class SettingsView extends ActivatableViewAndModel root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener); navigation.addListener(navigationListener); - if (root.getSelectionModel().getSelectedItem() == preferencesTab) + Tab selectedItem = root.getSelectionModel().getSelectedItem(); + if (selectedItem == preferencesTab) navigation.navigateTo(MainView.class, SettingsView.class, PreferencesView.class); - else + else if (selectedItem == networkSettingsTab) navigation.navigateTo(MainView.class, SettingsView.class, NetworkSettingsView.class); + else if (selectedItem == aboutTab) + navigation.navigateTo(MainView.class, SettingsView.class, AboutView.class); } @Override @@ -83,6 +89,7 @@ public class SettingsView extends ActivatableViewAndModel if (view instanceof PreferencesView) tab = preferencesTab; else if (view instanceof NetworkSettingsView) tab = networkSettingsTab; + else if (view instanceof AboutView) tab = aboutTab; else throw new IllegalArgumentException("Navigation to " + viewClass + " is not supported"); tab.setContent(view.getRoot()); diff --git a/gui/src/main/java/io/bitsquare/gui/main/settings/about/AboutView.fxml b/gui/src/main/java/io/bitsquare/gui/main/settings/about/AboutView.fxml new file mode 100644 index 0000000000..d31778785a --- /dev/null +++ b/gui/src/main/java/io/bitsquare/gui/main/settings/about/AboutView.fxml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + diff --git a/gui/src/main/java/io/bitsquare/gui/main/settings/about/AboutView.java b/gui/src/main/java/io/bitsquare/gui/main/settings/about/AboutView.java new file mode 100644 index 0000000000..50fa45e47f --- /dev/null +++ b/gui/src/main/java/io/bitsquare/gui/main/settings/about/AboutView.java @@ -0,0 +1,101 @@ +/* + * 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 . + */ + +package io.bitsquare.gui.main.settings.about; + +import io.bitsquare.app.Version; +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.HyperlinkWithIcon; +import io.bitsquare.gui.components.TitledGroupBg; +import io.bitsquare.gui.util.Layout; +import javafx.geometry.HPos; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; + +import javax.inject.Inject; + +import static io.bitsquare.gui.util.FormBuilder.*; + +@FxmlView +public class AboutView extends ActivatableViewAndModel { + + private int gridRow = 0; + + + @Inject + public AboutView() { + super(); + } + + public void initialize() { + TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, 4, "About Bitsquare"); + GridPane.setColumnSpan(titledGroupBg, 2); + Label label = addLabel(root, gridRow, "Bitsquare is an open source project and a decentralized network of users who want to " + + "exchange Bitcoin with national currencies or alternative crypto currencies in a privacy protecting way.\n" + + "Learn more about Bitsquare on our project web page.", Layout.FIRST_ROW_DISTANCE); + label.setWrapText(true); + GridPane.setColumnSpan(label, 2); + GridPane.setHalignment(label, HPos.LEFT); + HyperlinkWithIcon hyperlinkWithIcon = addHyperlinkWithIcon(root, ++gridRow, "Bitsquare web page", "https://bitsquare.io"); + GridPane.setColumnSpan(hyperlinkWithIcon, 2); + hyperlinkWithIcon = addHyperlinkWithIcon(root, ++gridRow, "Source code", "https://github.com/bitsquare/bitsquare"); + GridPane.setColumnSpan(hyperlinkWithIcon, 2); + hyperlinkWithIcon = addHyperlinkWithIcon(root, ++gridRow, "AGPL License", "https://github.com/bitsquare/bitsquare/blob/master/LICENSE"); + GridPane.setColumnSpan(hyperlinkWithIcon, 2); + + titledGroupBg = addTitledGroupBg(root, ++gridRow, 3, "Support Bitsquare", Layout.GROUP_DISTANCE); + GridPane.setColumnSpan(titledGroupBg, 2); + label = addLabel(root, gridRow, "Bitsquare is not a company but a community project and open for participation. If you want to participate check out our web page.", Layout.FIRST_ROW_AND_GROUP_DISTANCE); + label.setWrapText(true); + GridPane.setColumnSpan(label, 2); + GridPane.setHalignment(label, HPos.LEFT); + hyperlinkWithIcon = addHyperlinkWithIcon(root, ++gridRow, "Contribute", "https://bitsquare.io/contribute"); + GridPane.setColumnSpan(hyperlinkWithIcon, 2); + hyperlinkWithIcon = addHyperlinkWithIcon(root, ++gridRow, "Donate", "https://bitsquare.io/contribute/#donation"); + GridPane.setColumnSpan(hyperlinkWithIcon, 2); + + titledGroupBg = addTitledGroupBg(root, ++gridRow, 3, "Market price API providers", Layout.GROUP_DISTANCE); + GridPane.setColumnSpan(titledGroupBg, 2); + label = addLabel(root, gridRow, "Bitsquare uses market price feed providers for displaying the current exchange rate.", Layout.FIRST_ROW_AND_GROUP_DISTANCE); + label.setWrapText(true); + GridPane.setColumnSpan(label, 2); + GridPane.setHalignment(label, HPos.LEFT); + addLabelHyperlinkWithIcon(root, ++gridRow, "Market price API provider for fiat: ", "BitcoinAverage", "https://bitcoinaverage.com"); + addLabelHyperlinkWithIcon(root, ++gridRow, "Market price API provider for altcoins: ", "Poloniex", "http://poloniex.com"); + + titledGroupBg = addTitledGroupBg(root, ++gridRow, 2, "Version details", Layout.GROUP_DISTANCE); + GridPane.setColumnSpan(titledGroupBg, 2); + addLabelTextField(root, gridRow, "Application version:", Version.VERSION, Layout.FIRST_ROW_AND_GROUP_DISTANCE); + addLabelTextField(root, ++gridRow, "Versions of subsystems:", + "Network version: " + Version.P2P_NETWORK_VERSION + + "; P2P message version: " + Version.getP2PMessageVersion() + + "; Local DB version: " + Version.LOCAL_DB_VERSION + + "; Trade protocol version: " + Version.TRADE_PROTOCOL_VERSION); + } + + @Override + public void activate() { + } + + @Override + public void deactivate() { + } + +} + diff --git a/gui/src/main/java/io/bitsquare/gui/util/FormBuilder.java b/gui/src/main/java/io/bitsquare/gui/util/FormBuilder.java index 4876ff8cdc..333bcd8eda 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/FormBuilder.java +++ b/gui/src/main/java/io/bitsquare/gui/util/FormBuilder.java @@ -21,6 +21,7 @@ import de.jensd.fx.fontawesome.AwesomeIcon; import io.bitsquare.common.util.Tuple2; import io.bitsquare.common.util.Tuple3; import io.bitsquare.common.util.Tuple4; +import io.bitsquare.common.util.Utilities; import io.bitsquare.gui.components.*; import javafx.geometry.HPos; import javafx.geometry.Insets; @@ -162,12 +163,14 @@ public class FormBuilder { // HyperlinkWithIcon /////////////////////////////////////////////////////////////////////////////////////////// - public static HyperlinkWithIcon addHyperlinkWithIcon(GridPane gridPane, int rowIndex, String url) { - return addHyperlinkWithIcon(gridPane, rowIndex, url, 0); + + public static HyperlinkWithIcon addHyperlinkWithIcon(GridPane gridPane, int rowIndex, String title, String url) { + return addHyperlinkWithIcon(gridPane, rowIndex, title, url, 0); } - public static HyperlinkWithIcon addHyperlinkWithIcon(GridPane gridPane, int rowIndex, String url, double top) { - HyperlinkWithIcon hyperlinkWithIcon = new HyperlinkWithIcon(url, AwesomeIcon.EXTERNAL_LINK); + public static HyperlinkWithIcon addHyperlinkWithIcon(GridPane gridPane, int rowIndex, String title, String url, double top) { + HyperlinkWithIcon hyperlinkWithIcon = new HyperlinkWithIcon(title, AwesomeIcon.EXTERNAL_LINK); + hyperlinkWithIcon.setOnAction(e -> Utilities.openWebPage(url)); GridPane.setRowIndex(hyperlinkWithIcon, rowIndex); GridPane.setColumnIndex(hyperlinkWithIcon, 0); GridPane.setMargin(hyperlinkWithIcon, new Insets(top, 0, 0, -4)); @@ -180,18 +183,15 @@ public class FormBuilder { // Label + HyperlinkWithIcon /////////////////////////////////////////////////////////////////////////////////////////// - public static Tuple2 addLabelHyperlinkWithIcon(GridPane gridPane, int rowIndex, String title) { - return addLabelHyperlinkWithIcon(gridPane, rowIndex, title, "", 0); + public static Tuple2 addLabelHyperlinkWithIcon(GridPane gridPane, int rowIndex, String labelTitle, String title, String url) { + return addLabelHyperlinkWithIcon(gridPane, rowIndex, labelTitle, title, url, 0); } - public static Tuple2 addLabelHyperlinkWithIcon(GridPane gridPane, int rowIndex, String title, String url) { - return addLabelHyperlinkWithIcon(gridPane, rowIndex, title, url, 0); - } + public static Tuple2 addLabelHyperlinkWithIcon(GridPane gridPane, int rowIndex, String labelTitle, String title, String url, double top) { + Label label = addLabel(gridPane, rowIndex, labelTitle, top); - public static Tuple2 addLabelHyperlinkWithIcon(GridPane gridPane, int rowIndex, String title, String url, double top) { - Label label = addLabel(gridPane, rowIndex, title, top); - - HyperlinkWithIcon hyperlinkWithIcon = new HyperlinkWithIcon(url, AwesomeIcon.EXTERNAL_LINK); + HyperlinkWithIcon hyperlinkWithIcon = new HyperlinkWithIcon(title, AwesomeIcon.EXTERNAL_LINK); + hyperlinkWithIcon.setOnAction(e -> Utilities.openWebPage(url)); GridPane.setRowIndex(hyperlinkWithIcon, rowIndex); GridPane.setColumnIndex(hyperlinkWithIcon, 1); GridPane.setMargin(hyperlinkWithIcon, new Insets(top, 0, 0, -4)); diff --git a/gui/src/main/resources/images/invert.png b/gui/src/main/resources/images/invert.png new file mode 100644 index 0000000000..871b5fc89e Binary files /dev/null and b/gui/src/main/resources/images/invert.png differ diff --git a/gui/src/main/resources/images/invert@2x.png b/gui/src/main/resources/images/invert@2x.png new file mode 100644 index 0000000000..c215eb0071 Binary files /dev/null and b/gui/src/main/resources/images/invert@2x.png differ