diff --git a/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapOfferPayload.java b/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapOfferPayload.java index 2ba5ca2892f..7e9b60db0a3 100644 --- a/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapOfferPayload.java +++ b/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapOfferPayload.java @@ -17,6 +17,7 @@ package bisq.core.offer.bsq_swap; +import bisq.core.monetary.Price; import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferPayloadBase; import bisq.core.payment.BsqSwapAccount; @@ -65,6 +66,21 @@ public static BsqSwapOfferPayload from(BsqSwapOfferPayload original, original.getProtocolVersion() ); } + public static BsqSwapOfferPayload from(BsqSwapOfferPayload original, Price price) { + return new BsqSwapOfferPayload(original.getId(), + original.getDate(), + original.getOwnerNodeAddress(), + original.getPubKeyRing(), + original.getDirection(), + price.getValue(), + original.getAmount(), + original.getMinAmount(), + original.proofOfWork, + original.getExtraDataMap(), + original.getVersionNr(), + original.getProtocolVersion() + ); + } private final ProofOfWork proofOfWork; diff --git a/core/src/main/java/bisq/core/offer/bsq_swap/OpenBsqSwapOfferService.java b/core/src/main/java/bisq/core/offer/bsq_swap/OpenBsqSwapOfferService.java index 70735a57064..5b71b8f45a9 100644 --- a/core/src/main/java/bisq/core/offer/bsq_swap/OpenBsqSwapOfferService.java +++ b/core/src/main/java/bisq/core/offer/bsq_swap/OpenBsqSwapOfferService.java @@ -253,6 +253,27 @@ public void activateOpenOffer(OpenOffer openOffer, openOfferManager.activateOpenOffer(openOffer, resultHandler, errorMessageHandler); } + public void editOpenOfferStart(OpenOffer openOffer, + ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { + openOfferManager.editOpenOfferStart(openOffer, resultHandler, errorMessageHandler); + } + + public void editOpenOfferCancel(OpenOffer openOffer, + OpenOffer.State initialState, + ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { + openOfferManager.editOpenOfferCancel(openOffer, initialState, resultHandler, errorMessageHandler); + } + + + public void editOpenOfferPublish(Offer offer, + OpenOffer.State initialState, + ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { + openOfferManager.editOpenOfferPublish(offer, 0L, initialState, resultHandler, errorMessageHandler); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Package scope diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index a72a29d003c..a510125d4c7 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -648,6 +648,7 @@ portfolio.tab.history=History portfolio.tab.bsqSwap=Unconfirmed BSQ swaps portfolio.tab.failed=Failed portfolio.tab.editOpenOffer=Edit offer +portfolio.tab.editBsqSwapOpenOffer=Edit BSQ swap offer portfolio.tab.duplicateOffer=Duplicate offer portfolio.context.offerLikeThis=Create new offer like this... portfolio.context.notYourOffer=You can only duplicate offers where you were the maker. diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.java index 4334c6de69f..b880bd35569 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.java @@ -1119,7 +1119,7 @@ private void addAmountPriceFields() { xLabel.getStyleClass().addAll("opaque-icon-character"); // price - Tuple3 priceValueCurrencyBoxTuple = getNonEditableValueBox(); + Tuple3 priceValueCurrencyBoxTuple = getNonEditableValueBox(); priceValueCurrencyBox = priceValueCurrencyBoxTuple.first; priceTextField = priceValueCurrencyBoxTuple.second; priceCurrencyLabel = priceValueCurrencyBoxTuple.third; @@ -1157,7 +1157,7 @@ private void addAmountPriceFields() { } private void addSecondRow() { - Tuple3 priceAsPercentageTuple = getNonEditableValueBox(); + Tuple3 priceAsPercentageTuple = getNonEditableValueBox(); priceAsPercentageValueCurrencyBox = priceAsPercentageTuple.first; priceAsPercentageTextField = priceAsPercentageTuple.second; priceAsPercentageLabel = priceAsPercentageTuple.third; @@ -1171,7 +1171,7 @@ private void addSecondRow() { priceAsPercentageLabel.setText("%"); - Tuple3 amountValueCurrencyBoxTuple = getNonEditableValueBox(); + Tuple3 amountValueCurrencyBoxTuple = getNonEditableValueBox(); amountRangeTextField = amountValueCurrencyBoxTuple.second; minAmountValueCurrencyBox = amountValueCurrencyBoxTuple.first; diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/edit_offer/BsqSwapEditOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/edit_offer/BsqSwapEditOfferDataModel.java new file mode 100644 index 00000000000..c4ae9b5e59e --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/edit_offer/BsqSwapEditOfferDataModel.java @@ -0,0 +1,100 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, +see . + */ + +package bisq.desktop.main.offer.bsq_swap.edit_offer; + +import bisq.desktop.main.offer.bsq_swap.BsqSwapOfferDataModel; + +import bisq.core.offer.Offer; +import bisq.core.offer.OpenOffer; +import bisq.core.offer.bsq_swap.BsqSwapOfferModel; +import bisq.core.offer.bsq_swap.BsqSwapOfferPayload; +import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService; +import bisq.core.user.User; +import bisq.core.util.FormattingUtils; +import bisq.core.util.coin.CoinFormatter; + +import bisq.network.p2p.P2PService; + +import bisq.common.handlers.ErrorMessageHandler; +import bisq.common.handlers.ResultHandler; + +import javax.inject.Inject; +import javax.inject.Named; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class BsqSwapEditOfferDataModel extends BsqSwapOfferDataModel { + private final OpenBsqSwapOfferService openBsqSwapOfferService; + protected OpenOffer openOffer; + protected OpenOffer.State initialState; + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + BsqSwapEditOfferDataModel(BsqSwapOfferModel bsqSwapOfferModel, + OpenBsqSwapOfferService openBsqSwapOfferService, + User user, + P2PService p2PService, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) { + super(bsqSwapOfferModel, + user, + p2PService, + btcFormatter); + + this.openBsqSwapOfferService = openBsqSwapOfferService; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void applyOpenOffer(OpenOffer openOffer) { + this.openOffer = openOffer; + this.initialState = openOffer.getState(); + + Offer offer = openOffer.getOffer(); + bsqSwapOfferModel.init(offer.getDirection(), true, offer); + } + + public void onStartEditOffer(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + openBsqSwapOfferService.editOpenOfferStart(openOffer, resultHandler, errorMessageHandler); + } + + public void onCancelEditOffer(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + if (openOffer != null) + openBsqSwapOfferService.editOpenOfferCancel(openOffer, initialState, resultHandler, errorMessageHandler); + } + + public void onPublishOffer(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + BsqSwapOfferPayload original = openOffer.getOffer().getBsqSwapOfferPayload().orElseThrow(); + BsqSwapOfferPayload newPayload = BsqSwapOfferPayload.from(original, getPrice().get()); + + openBsqSwapOfferService.editOpenOfferPublish(new Offer(newPayload), initialState, () -> { + openOffer = null; + resultHandler.handleResult(); + }, errorMessageHandler); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/edit_offer/BsqSwapEditOfferView.fxml b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/edit_offer/BsqSwapEditOfferView.fxml new file mode 100644 index 00000000000..3a59fb92f9a --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/edit_offer/BsqSwapEditOfferView.fxml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/edit_offer/BsqSwapEditOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/edit_offer/BsqSwapEditOfferView.java new file mode 100644 index 00000000000..3cec938a3e8 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/edit_offer/BsqSwapEditOfferView.java @@ -0,0 +1,458 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.desktop.main.offer.bsq_swap.edit_offer; + +import bisq.desktop.Navigation; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.AutoTooltipButton; +import bisq.desktop.components.AutoTooltipLabel; +import bisq.desktop.components.BusyAnimation; +import bisq.desktop.components.InfoInputTextField; +import bisq.desktop.components.InputTextField; +import bisq.desktop.components.TitledGroupBg; +import bisq.desktop.main.offer.bsq_swap.BsqSwapOfferView; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.util.Layout; + +import bisq.core.locale.CurrencyUtil; +import bisq.core.locale.Res; +import bisq.core.offer.OpenOffer; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.payload.PaymentMethod; +import bisq.core.user.DontShowAgainLookup; + +import bisq.common.util.Tuple2; +import bisq.common.util.Tuple3; +import bisq.common.util.Tuple4; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; + +import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; + +import com.jfoenix.controls.JFXTextField; + +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; + +import javafx.beans.value.ChangeListener; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.offer.bsq_swap.BsqSwapOfferModel.BSQ; +import static bisq.desktop.util.FormBuilder.*; + +@FxmlView +@Slf4j +public class BsqSwapEditOfferView extends BsqSwapOfferView { + private TextField paymentMethodTextField, currencyTextField; + private InputTextField minAmountTextField, priceTextField, volumeTextField; + private ChangeListener priceFocusedListener; + private boolean isActivated; + private BusyAnimation busyAnimation; + private Label spinnerInfoLabel; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public BsqSwapEditOfferView(BsqSwapEditOfferViewModel model, + Navigation navigation) { + super(model, navigation, null); + } + + @Override + protected void activate() { + super.activate(); + + if (model.dataModel.isTabSelected()) { + doActivate(); + } + } + + private void doActivate() { + if (!isActivated) { + isActivated = true; + + addListeners(); + addBindings(); + } + } + + public void onClose() { + model.onCancelEditOffer(() -> {}, errorMessage -> { + log.error(errorMessage); + new Popup().warning(Res.get("editOffer.failed", errorMessage)).show(); + }); + } + + @Override + protected void deactivate() { + super.deactivate(); + + if (isActivated) { + isActivated = false; + removeListeners(); + removeBindings(); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void applyOpenOffer(OpenOffer openOffer) { + model.applyOpenOffer(openOffer); + + model.onStartEditOffer(() -> {}, errorMessage -> { + log.error(errorMessage); + new Popup().warning(Res.get("editOffer.failed", errorMessage)) + .onClose(this::close) + .show(); + }); + + updateElementsWithDirection(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI actions + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onTabSelected(boolean isSelected) { + if (isSelected && !model.dataModel.isTabSelected()) { + doActivate(); + } + + isActivated = isSelected; + model.dataModel.onTabSelected(isSelected); + } + + @Override + protected void onCancel1() { + close(); + } + + @Override + protected void onCancel2() { + close(); + } + + @Override + protected void onAction() { + if (!model.dataModel.canPlaceOrTakeOffer()) { + return; + } + + if (!isMissingFundsPopupOpen && model.dataModel.hasMissingFunds()) { + checkForMissingFunds(model.dataModel.getMissingFunds().get()); + return; + } + + isMissingFundsPopupOpen = false; + model.isNextButtonDisabled.setValue(true); + model.isCancelButtonDisabled.setValue(true); + spinnerInfoLabel.setText(Res.get("editOffer.publishOffer")); + busyAnimation.play(); + + model.onPublishOffer(() -> { + String key = "editOfferSuccess"; + if (DontShowAgainLookup.showAgain(key)) { + new Popup() + .feedback(Res.get("editOffer.success")) + .dontShowAgainId(key) + .show(); + } + spinnerInfoLabel.setText(""); + busyAnimation.stop(); + close(); + }, (message) -> { + log.error(message); + spinnerInfoLabel.setText(""); + busyAnimation.stop(); + model.isNextButtonDisabled.setValue(false); + model.isCancelButtonDisabled.setValue(false); + new Popup().warning(Res.get("editOffer.failed", message)).show(); + }); + + requestFocus(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Bindings, Listeners + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void createListeners() { + super.createListeners(); + + priceFocusedListener = (o, oldValue, newValue) -> { + if (oldValue && !newValue) model.onFocusOutPriceTextField(); + priceTextField.setText(model.price.get()); + }; + } + + @Override + protected void addListeners() { + // focus out + priceTextField.focusedProperty().addListener(priceFocusedListener); + } + + @Override + protected void removeListeners() { + super.removeListeners(); + + // focus out + priceTextField.focusedProperty().removeListener(priceFocusedListener); + } + + @Override + protected void addBindings() { + amountTextField.textProperty().bindBidirectional(model.amount); + minAmountTextField.textProperty().bindBidirectional(model.minAmount); + priceTextField.textProperty().bindBidirectional(model.price); + volumeTextField.textProperty().bindBidirectional(model.volume); + + // validation + priceTextField.validationResultProperty().bind(model.priceValidationResult); + + nextButton.disableProperty().bind(model.isNextButtonDisabled); + cancelButton1.disableProperty().bind(model.isCancelButtonDisabled); + + // trading account + paymentAccountTitledGroupBg.managedProperty().bind(paymentAccountTitledGroupBg.visibleProperty()); + } + + @Override + protected void removeBindings() { + amountTextField.textProperty().unbindBidirectional(model.amount); + minAmountTextField.textProperty().unbindBidirectional(model.minAmount); + priceTextField.textProperty().unbindBidirectional(model.price); + volumeTextField.textProperty().unbindBidirectional(model.volume); + volumeTextField.promptTextProperty().unbindBidirectional(model.volume); + + // Validation + priceTextField.validationResultProperty().unbind(); + + nextButton.disableProperty().unbind(); + cancelButton1.disableProperty().unbind(); + + // trading account + paymentAccountTitledGroupBg.managedProperty().unbind(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Build UI elements + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void addPaymentAccountGroup() { + paymentAccountTitledGroupBg = addTitledGroupBg(gridPane, gridRow, 1, Res.get("shared.editOffer")); + GridPane.setColumnSpan(paymentAccountTitledGroupBg, 2); + + // We use the addComboBoxTopLabelTextField only for convenience for having the expected layout + Tuple4, Label, TextField, HBox> paymentAccountTuple = addComboBoxTopLabelTextField(gridPane, + gridRow, "", Res.get("shared.paymentMethod"), Layout.FIRST_ROW_DISTANCE); + HBox hBox = paymentAccountTuple.fourth; + hBox.getChildren().remove(paymentAccountTuple.first); + + paymentMethodTextField = paymentAccountTuple.third; + paymentMethodTextField.setMinWidth(300); + paymentMethodTextField.setEditable(false); + paymentMethodTextField.setMouseTransparent(true); + paymentMethodTextField.setFocusTraversable(false); + paymentMethodTextField.setText(PaymentMethod.BSQ_SWAP.getDisplayString()); + + currencyTextField = new JFXTextField(CurrencyUtil.getNameByCode(BSQ)); + currencyTextField.setMinWidth(300); + currencyTextField.setEditable(false); + currencyTextField.setMouseTransparent(true); + currencyTextField.setFocusTraversable(false); + + Tuple2 tradeCurrencyTuple = getTopLabelWithVBox(Res.get("shared.tradeCurrency"), currencyTextField); + VBox vBox = tradeCurrencyTuple.second; + HBox.setMargin(vBox, new Insets(5, 0, 0, 0)); + + hBox.setSpacing(30); + hBox.setAlignment(Pos.CENTER_LEFT); + hBox.setPadding(new Insets(10, 0, 18, 0)); + hBox.getChildren().add(vBox); + } + + @Override + protected void addAmountPriceGroup() { + TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 2, + Res.get("createOffer.setAmountPrice"), Layout.COMPACT_GROUP_DISTANCE); + GridPane.setColumnSpan(titledGroupBg, 2); + + addFirstRow(); + addSecondRow(); + } + + private void addFirstRow() { + // amountBox + Tuple3 amountValueCurrencyBoxTuple = getNonEditableValueBox(); + amountValueCurrencyBox = amountValueCurrencyBoxTuple.first; + amountTextField = amountValueCurrencyBoxTuple.second; + Tuple2 amountInputBoxTuple = getTradeInputBox(amountValueCurrencyBox, ""); + amountDescriptionLabel = amountInputBoxTuple.first; + VBox amountBox = amountInputBoxTuple.second; + + // x + xLabel = new Label(); + xIcon = getIconForLabel(MaterialDesignIcon.CLOSE, "2em", xLabel); + xIcon.getStyleClass().add("opaque-icon"); + xLabel.getStyleClass().add("opaque-icon-character"); + + // price + Tuple3 priceValueCurrencyBoxTuple = getEditableValueBox( + Res.get("createOffer.price.prompt")); + priceValueCurrencyBox = priceValueCurrencyBoxTuple.first; + priceTextField = priceValueCurrencyBoxTuple.second; + priceCurrencyLabel = priceValueCurrencyBoxTuple.third; + priceCurrencyLabel.setText("BTC"); + Tuple2 priceInputBoxTuple = getTradeInputBox(priceValueCurrencyBox, ""); + priceDescriptionLabel = priceInputBoxTuple.first; + priceDescriptionLabel.setText(CurrencyUtil.getPriceWithCurrencyCode(BSQ, "shared.fixedPriceInCurForCur")); + + + getSmallIconForLabel(MaterialDesignIcon.LOCK, priceDescriptionLabel, "small-icon-label"); + + VBox fixedPriceBox = priceInputBoxTuple.second; + + // = + resultLabel = new AutoTooltipLabel("="); + resultLabel.getStyleClass().add("opaque-icon-character"); + + // volume + Tuple3 volumeValueCurrencyBoxTuple = getNonEditableValueBoxWithInfo(); + volumeValueCurrencyBox = volumeValueCurrencyBoxTuple.first; + InfoInputTextField volumeInfoInputTextField = volumeValueCurrencyBoxTuple.second; + volumeTextField = volumeInfoInputTextField.getInputTextField(); + volumeTextField.setPromptText(Res.get("createOffer.volume.prompt", BSQ)); + volumeCurrencyLabel = volumeValueCurrencyBoxTuple.third; + volumeCurrencyLabel.setText(BSQ); + Tuple2 volumeInputBoxTuple = getTradeInputBox(volumeValueCurrencyBox, ""); + volumeDescriptionLabel = volumeInputBoxTuple.first; + VBox volumeBox = volumeInputBoxTuple.second; + + firstRowHBox = new HBox(); + firstRowHBox.setSpacing(5); + firstRowHBox.setAlignment(Pos.CENTER_LEFT); + firstRowHBox.getChildren().addAll(amountBox, xLabel, fixedPriceBox, resultLabel, volumeBox); + GridPane.setColumnSpan(firstRowHBox, 2); + GridPane.setRowIndex(firstRowHBox, gridRow); + GridPane.setMargin(firstRowHBox, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 10, 0, 0)); + gridPane.getChildren().add(firstRowHBox); + } + + private void addSecondRow() { + Tuple3 amountValueCurrencyBoxTuple = getNonEditableValueBox(); + minAmountValueCurrencyBox = amountValueCurrencyBoxTuple.first; + minAmountTextField = amountValueCurrencyBoxTuple.second; + Tuple2 amountInputBoxTuple = getTradeInputBox(minAmountValueCurrencyBox, Res.get("createOffer.amountPriceBox.minAmountDescription")); + + secondRowHBox = new HBox(); + secondRowHBox.setSpacing(5); + secondRowHBox.setAlignment(Pos.CENTER_LEFT); + secondRowHBox.getChildren().add(amountInputBoxTuple.second); + GridPane.setColumnSpan(secondRowHBox, 2); + GridPane.setRowIndex(secondRowHBox, ++gridRow); + GridPane.setColumnIndex(secondRowHBox, 0); + GridPane.setMargin(secondRowHBox, new Insets(0, 10, 10, 0)); + gridPane.getChildren().add(secondRowHBox); + } + + @Override + protected void addNextAndCancelButtons() { + Tuple4 tuple = addButtonBusyAnimationLabelAfterGroup(gridPane, + ++gridRow, Res.get("editOffer.confirmEdit")); + + nextButtonBar = tuple.fourth; + + nextButton = tuple.first; + nextButton.setMaxWidth(250); + nextButton.setOnAction(e -> onAction()); + + busyAnimation = tuple.second; + spinnerInfoLabel = tuple.third; + + cancelButton1 = new AutoTooltipButton(Res.get("shared.cancel")); + cancelButton1.setDefaultButton(false); + cancelButton1.setMaxWidth(250); + cancelButton1.setOnAction(e -> close()); + nextButtonBar.getChildren().add(cancelButton1); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + private void updateElementsWithDirection() { + boolean isBuyOffer = model.dataModel.isBuyOffer(); + + String volumeDescription = isBuyOffer + ? Res.get("createOffer.amountPriceBox.buy.volumeDescription", BSQ) + : Res.get("createOffer.amountPriceBox.sell.volumeDescription", BSQ); + + volumeDescriptionLabel.setText(volumeDescription); + + String amountDescription = isBuyOffer + ? Res.get("createOffer.amountPriceBox.amountDescription", Res.get("shared.buy")) + : Res.get("createOffer.amountPriceBox.amountDescription", Res.get("shared.sell")); + + amountDescriptionLabel.setText(amountDescription); + + ImageView iconView = new ImageView(); + iconView.setId(isBuyOffer ? "image-buy-white" : "image-sell-white"); + nextButton.setGraphic(iconView); + nextButton.setId(isBuyOffer ? "sell-button-big" : "buy-button-big"); + } + + @Override + protected void checkForMissingFunds(Coin missing) { + if (missing.isPositive() && !isMissingFundsPopupOpen) { + isMissingFundsPopupOpen = true; + String wallet = model.dataModel.isBuyer() ? "BSQ" : "BTC"; + String warning = Res.get("createOffer.bsqSwap.missingFunds.maker", + wallet, model.getMissingFunds(missing)); + new Popup().warning(warning) + .actionButtonText(Res.get("shared.continueAnyway")) + .onAction(() -> onAction()) + .onClose(() -> { + isMissingFundsPopupOpen = false; + }) + .show(); + } + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/edit_offer/BsqSwapEditOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/edit_offer/BsqSwapEditOfferViewModel.java new file mode 100644 index 00000000000..573054a21b9 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/edit_offer/BsqSwapEditOfferViewModel.java @@ -0,0 +1,183 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.desktop.main.offer.bsq_swap.edit_offer; + +import bisq.desktop.common.model.ViewModel; +import bisq.desktop.main.offer.bsq_swap.BsqSwapOfferViewModel; +import bisq.desktop.util.validation.BtcValidator; + +import bisq.core.account.witness.AccountAgeWitnessService; +import bisq.core.btc.wallet.Restrictions; +import bisq.core.monetary.Price; +import bisq.core.monetary.Volume; +import bisq.core.offer.OpenOffer; +import bisq.core.payment.payload.PaymentMethod; +import bisq.core.util.FormattingUtils; +import bisq.core.util.VolumeUtil; +import bisq.core.util.coin.BsqFormatter; +import bisq.core.util.coin.CoinFormatter; +import bisq.core.util.validation.AltcoinValidator; +import bisq.core.util.validation.InputValidator; + +import bisq.common.handlers.ErrorMessageHandler; +import bisq.common.handlers.ResultHandler; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; +import javax.inject.Named; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.offer.bsq_swap.BsqSwapOfferModel.BSQ; + +@Slf4j +class BsqSwapEditOfferViewModel extends BsqSwapOfferViewModel implements ViewModel { + private final BtcValidator btcValidator; + private final AltcoinValidator altcoinValidator; + + final StringProperty amount = new SimpleStringProperty(); + final StringProperty minAmount = new SimpleStringProperty(); + final StringProperty price = new SimpleStringProperty(); + final StringProperty volume = new SimpleStringProperty(); + + final ObjectProperty priceValidationResult = new SimpleObjectProperty<>(); + + final BooleanProperty isNextButtonDisabled = new SimpleBooleanProperty(false); + final BooleanProperty isCancelButtonDisabled = new SimpleBooleanProperty(false); + + private ChangeListener priceListener; + private ChangeListener volumeListener; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + BsqSwapEditOfferViewModel(BsqSwapEditOfferDataModel dataModel, + AltcoinValidator altcoinValidator, + BtcValidator btcValidator, + AccountAgeWitnessService accountAgeWitnessService, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, + BsqFormatter bsqFormatter) { + super(dataModel, btcFormatter, bsqFormatter, accountAgeWitnessService); + + this.altcoinValidator = altcoinValidator; + this.btcValidator = btcValidator; + } + + @Override + protected void activate() { + addBindings(); + addListeners(); + + minAmount.set(btcFormatter.formatCoin(dataModel.getMinAmount().get())); + amount.set(btcFormatter.formatCoin(dataModel.getBtcAmount().get())); + price.set(FormattingUtils.formatPrice(dataModel.getPrice().get())); + volume.set(VolumeUtil.formatVolume(dataModel.getVolume().get())); + + isNextButtonDisabled.set(false); + isCancelButtonDisabled.set(false); + } + + @Override + protected void deactivate() { + removeBindings(); + removeListeners(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void applyOpenOffer(OpenOffer openOffer) { + dataModel.applyOpenOffer(openOffer); + + btcValidator.setMaxValue(PaymentMethod.BSQ_SWAP.getMaxTradeLimitAsCoin(BSQ)); + btcValidator.setMaxTradeLimit(Coin.valueOf(dataModel.getMaxTradeLimit())); + btcValidator.setMinValue(Restrictions.getMinTradeAmount()); + } + + public void onStartEditOffer(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + dataModel.onStartEditOffer(resultHandler, errorMessageHandler); + } + + public void onCancelEditOffer(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + dataModel.onCancelEditOffer(resultHandler, errorMessageHandler); + } + + public void onPublishOffer(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + dataModel.onPublishOffer(resultHandler, errorMessageHandler); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Focus + /////////////////////////////////////////////////////////////////////////////////////////// + + public void onFocusOutPriceTextField() { + InputValidator.ValidationResult result = altcoinValidator.validate(price.get()); + priceValidationResult.set(result); + isNextButtonDisabled.set(!result.isValid); + + if (result.isValid) { + dataModel.setPrice(Price.parse(BSQ, price.get())); + dataModel.calculateVolume(); + dataModel.calculateInputAndPayout(); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Listeners + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void createListeners() { + priceListener = (ov, oldValue, newValue) -> { + price.set(newValue != null ? FormattingUtils.formatPrice(newValue) : ""); + }; + volumeListener = (ov, oldValue, newValue) -> { + volume.set(VolumeUtil.formatVolume(newValue)); + }; + } + + @Override + protected void addListeners() { + // Binding with Bindings.createObjectBinding does not work because of bi-directional binding + dataModel.getPrice().addListener(priceListener); + dataModel.getVolume().addListener(volumeListener); + } + + @Override + protected void removeListeners() { + // Binding with Bindings.createObjectBinding does not work because of bi-directional binding + dataModel.getPrice().removeListener(priceListener); + dataModel.getVolume().removeListener(volumeListener); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferView.java index c65cd37a5c0..b2d1cbc907c 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferView.java @@ -503,7 +503,7 @@ private void addFirstRow() { xLabel.getStyleClass().addAll("opaque-icon-character"); // price - Tuple3 priceValueCurrencyBoxTuple = getNonEditableValueBox(); + Tuple3 priceValueCurrencyBoxTuple = getNonEditableValueBox(); priceValueCurrencyBox = priceValueCurrencyBoxTuple.first; priceTextField = priceValueCurrencyBoxTuple.second; priceCurrencyLabel = priceValueCurrencyBoxTuple.third; @@ -544,7 +544,7 @@ private void addFirstRow() { } private void addSecondRow() { - Tuple3 amountValueCurrencyBoxTuple = getNonEditableValueBox(); + Tuple3 amountValueCurrencyBoxTuple = getNonEditableValueBox(); minAmountValueCurrencyBox = amountValueCurrencyBoxTuple.first; minAmountTextField = amountValueCurrencyBoxTuple.second; diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index ff31fc0dca2..35c951f6b36 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -38,6 +38,7 @@ import bisq.desktop.main.funds.withdrawal.WithdrawalView; import bisq.desktop.main.offer.OfferView; import bisq.desktop.main.offer.OfferViewUtil; +import bisq.desktop.main.offer.bsq_swap.edit_offer.BsqSwapEditOfferView; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.BsqSwapOfferDetailsWindow; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; @@ -734,7 +735,12 @@ private void onRemoveOpenOffer(Offer offer) { private void onEditOpenOffer(Offer offer) { OpenOffer openOffer = model.getOpenOffer(offer); if (openOffer != null) { - navigation.navigateToWithData(openOffer, MainView.class, PortfolioView.class, EditOfferView.class); + if (openOffer.getOffer().isBsqSwapOffer()) { + navigation.navigateToWithData(openOffer, MainView.class, PortfolioView.class, BsqSwapEditOfferView.class); + } else { + navigation.navigateToWithData(openOffer, MainView.class, PortfolioView.class, EditOfferView.class); + + } } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.java index 46cc33712ce..76e55d01fd2 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.java @@ -23,6 +23,7 @@ import bisq.desktop.common.view.FxmlView; import bisq.desktop.common.view.View; import bisq.desktop.main.MainView; +import bisq.desktop.main.offer.bsq_swap.edit_offer.BsqSwapEditOfferView; import bisq.desktop.main.portfolio.bsqswaps.UnconfirmedBsqSwapsView; import bisq.desktop.main.portfolio.closedtrades.ClosedTradesView; import bisq.desktop.main.portfolio.duplicateoffer.DuplicateOfferView; @@ -58,7 +59,7 @@ public class PortfolioView extends ActivatableView { @FXML Tab openOffersTab, pendingTradesTab, closedTradesTab, bsqSwapTradesTab; - private Tab editOpenOfferTab, duplicateOfferTab; + private Tab editOpenOfferTab, bsqSwapEditOpenOfferTab, duplicateOfferTab; private final Tab failedTradesTab = new Tab(Res.get("portfolio.tab.failed").toUpperCase()); private Tab currentTab; private Navigation.Listener navigationListener; @@ -70,8 +71,10 @@ public class PortfolioView extends ActivatableView { private final FailedTradesManager failedTradesManager; private final OpenOfferManager openOfferManager; private EditOfferView editOfferView; + private BsqSwapEditOfferView bsqSwapEditOfferView; private DuplicateOfferView duplicateOfferView; private boolean editOpenOfferViewOpen; + private boolean bsqSwapEditOpenOfferViewOpen; private OpenOffer openOffer; private OpenOffersView openOffersView; private int initialTabCount = 0; @@ -114,12 +117,16 @@ else if (newValue == failedTradesTab) navigation.navigateTo(MainView.class, PortfolioView.class, FailedTradesView.class); else if (newValue == editOpenOfferTab) navigation.navigateTo(MainView.class, PortfolioView.class, EditOfferView.class); + else if (newValue == bsqSwapEditOpenOfferTab) + navigation.navigateTo(MainView.class, PortfolioView.class, BsqSwapEditOfferView.class); else if (newValue == duplicateOfferTab) { navigation.navigateTo(MainView.class, PortfolioView.class, DuplicateOfferView.class); } if (oldValue != null && oldValue == editOpenOfferTab) editOfferView.onTabSelected(false); + if (oldValue != null && oldValue == bsqSwapEditOpenOfferTab) + bsqSwapEditOfferView.onTabSelected(false); if (oldValue != null && oldValue == duplicateOfferTab) duplicateOfferView.onTabSelected(false); @@ -130,6 +137,8 @@ else if (newValue == duplicateOfferTab) { List removedTabs = change.getRemoved(); if (removedTabs.size() == 1 && removedTabs.get(0).equals(editOpenOfferTab)) onEditOpenOfferRemoved(); + if (removedTabs.size() == 1 && removedTabs.get(0).equals(bsqSwapEditOpenOfferTab)) + onBsqSwapEditOpenOfferRemoved(); if (removedTabs.size() == 1 && removedTabs.get(0).equals(duplicateOfferTab)) onDuplicateOfferRemoved(); }; @@ -145,6 +154,16 @@ private void onEditOpenOfferRemoved() { navigation.navigateTo(MainView.class, this.getClass(), OpenOffersView.class); } + private void onBsqSwapEditOpenOfferRemoved() { + bsqSwapEditOpenOfferViewOpen = false; + if (bsqSwapEditOfferView != null) { + bsqSwapEditOfferView.onClose(); + bsqSwapEditOfferView = null; + } + + navigation.navigateTo(MainView.class, this.getClass(), OpenOffersView.class); + } + private void onDuplicateOfferRemoved() { if (duplicateOfferView != null) { duplicateOfferView.onClose(); @@ -180,6 +199,9 @@ else if (root.getSelectionModel().getSelectedItem() == failedTradesTab) else if (root.getSelectionModel().getSelectedItem() == editOpenOfferTab) { navigation.navigateTo(MainView.class, PortfolioView.class, EditOfferView.class); if (editOfferView != null) editOfferView.onTabSelected(true); + } else if (root.getSelectionModel().getSelectedItem() == bsqSwapEditOpenOfferTab) { + navigation.navigateTo(MainView.class, PortfolioView.class, BsqSwapEditOfferView.class); + if (bsqSwapEditOfferView != null) bsqSwapEditOfferView.onTabSelected(true); } else if (root.getSelectionModel().getSelectedItem() == duplicateOfferTab) { navigation.navigateTo(MainView.class, PortfolioView.class, DuplicateOfferView.class); if (duplicateOfferView != null) duplicateOfferView.onTabSelected(true); @@ -200,6 +222,9 @@ private void loadView(Class viewClass, @Nullable Object data) { if (currentTab != null && currentTab != editOpenOfferTab) currentTab.setContent(null); + if (currentTab != null && currentTab != bsqSwapEditOpenOfferTab) + currentTab.setContent(null); + View view = viewLoader.load(viewClass); if (view instanceof OpenOffersView) { @@ -234,6 +259,28 @@ private void loadView(Class viewClass, @Nullable Object data) { view = viewLoader.load(OpenOffersView.class); selectOpenOffersView((OpenOffersView) view); } + } else if (view instanceof BsqSwapEditOfferView) { + if (data instanceof OpenOffer) { + openOffer = (OpenOffer) data; + } + if (openOffer != null) { + if (bsqSwapEditOfferView == null) { + bsqSwapEditOfferView = (BsqSwapEditOfferView) view; + bsqSwapEditOfferView.applyOpenOffer(openOffer); + bsqSwapEditOpenOfferTab = new Tab(Res.get("portfolio.tab.editBsqSwapOpenOffer").toUpperCase()); + bsqSwapEditOfferView.setCloseHandler(() -> { + root.getTabs().remove(bsqSwapEditOpenOfferTab); + }); + root.getTabs().add(bsqSwapEditOpenOfferTab); + } + if (currentTab != bsqSwapEditOpenOfferTab) + bsqSwapEditOfferView.onTabSelected(true); + + currentTab = bsqSwapEditOpenOfferTab; + } else { + view = viewLoader.load(OpenOffersView.class); + selectOpenOffersView((OpenOffersView) view); + } } else if (view instanceof DuplicateOfferView) { if (duplicateOfferView == null && data instanceof OfferPayload) { viewLoader.removeFromCache(viewClass); // remove cached dialog @@ -265,10 +312,14 @@ private void selectOpenOffersView(OpenOffersView view) { currentTab = openOffersTab; OpenOfferActionHandler openOfferActionHandler = openOffer -> { - if (!editOpenOfferViewOpen) { + if (!editOpenOfferViewOpen && !openOffer.getOffer().isBsqSwapOffer()) { editOpenOfferViewOpen = true; PortfolioView.this.openOffer = openOffer; navigation.navigateTo(MainView.class, PortfolioView.this.getClass(), EditOfferView.class); + } else if (!bsqSwapEditOpenOfferViewOpen && openOffer.getOffer().isBsqSwapOffer()) { + bsqSwapEditOpenOfferViewOpen = true; + PortfolioView.this.openOffer = openOffer; + navigation.navigateTo(MainView.class, PortfolioView.this.getClass(), BsqSwapEditOfferView.class); } else { log.error("You have already a \"Edit Offer\" tab open."); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java index f9234b65be5..7b59e80ff0d 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java @@ -797,21 +797,36 @@ public void updateItem(final OpenOfferListItem item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { - if (button == null) { - button = getRegularIconButton(MaterialDesignIcon.SHIELD_HALF_FULL); - boolean triggerPriceSet = item.getOpenOffer().getTriggerPrice() > 0; - button.setVisible(triggerPriceSet); - - if (model.dataModel.wasTriggered(item.getOpenOffer())) { - button.getGraphic().getStyleClass().add("warning"); - button.setTooltip(new Tooltip(Res.get("openOffer.triggered"))); + if (item.getOffer().isBsqSwapOffer()) { + if (button != null) { + button.setOnAction(null); + button = null; + } + if (item.getOpenOffer().isBsqSwapOfferHasMissingFunds()) { + Label label = new Label(); + Text icon = getRegularIconForLabel(MaterialDesignIcon.EYE_OFF, label, "opaque-icon"); + Tooltip.install(icon, new Tooltip(Res.get("openOffer.bsqSwap.missingFunds"))); + setGraphic(icon); } else { - button.getGraphic().getStyleClass().remove("warning"); - button.setTooltip(new Tooltip(Res.get("openOffer.triggerPrice", item.getTriggerPriceAsString()))); + setGraphic(null); } - setGraphic(button); + } else { + if (button == null) { + button = getRegularIconButton(MaterialDesignIcon.SHIELD_HALF_FULL); + boolean triggerPriceSet = item.getOpenOffer().getTriggerPrice() > 0; + button.setVisible(triggerPriceSet); + + if (model.dataModel.wasTriggered(item.getOpenOffer())) { + button.getGraphic().getStyleClass().add("warning"); + button.setTooltip(new Tooltip(Res.get("openOffer.triggered"))); + } else { + button.getGraphic().getStyleClass().remove("warning"); + button.setTooltip(new Tooltip(Res.get("openOffer.triggerPrice", item.getTriggerPriceAsString()))); + } + setGraphic(button); + } + button.setOnAction(event -> onEditOpenOffer(item.getOpenOffer())); } - button.setOnAction(event -> onEditOpenOffer(item.getOpenOffer())); } else { setGraphic(null); if (button != null) { @@ -839,26 +854,11 @@ public void updateItem(final OpenOfferListItem item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { - if (item.getOffer().isBsqSwapOffer()) { - if (button != null) { - button.setOnAction(null); - button = null; - } - if (item.getOpenOffer().isBsqSwapOfferHasMissingFunds()) { - Label label = new Label(); - Text icon = getRegularIconForLabel(MaterialDesignIcon.EYE_OFF, label, "opaque-icon"); - Tooltip.install(icon, new Tooltip(Res.get("openOffer.bsqSwap.missingFunds"))); - setGraphic(icon); - } else { - setGraphic(null); - } - } else { - if (button == null) { - button = getRegularIconButton(MaterialDesignIcon.PENCIL); - button.setTooltip(new Tooltip(Res.get("shared.editOffer"))); - button.setOnAction(event -> onEditOpenOffer(item.getOpenOffer())); - setGraphic(button); - } + if (button == null) { + button = getRegularIconButton(MaterialDesignIcon.PENCIL); + button.setTooltip(new Tooltip(Res.get("shared.editOffer"))); + button.setOnAction(event -> onEditOpenOffer(item.getOpenOffer())); + setGraphic(button); } } else { setGraphic(null); diff --git a/desktop/src/main/java/bisq/desktop/util/FormBuilder.java b/desktop/src/main/java/bisq/desktop/util/FormBuilder.java index 3d006b78544..90aca30c8a9 100644 --- a/desktop/src/main/java/bisq/desktop/util/FormBuilder.java +++ b/desktop/src/main/java/bisq/desktop/util/FormBuilder.java @@ -2171,9 +2171,9 @@ public static Tuple3 getEditableValueBoxWithInf return new Tuple3<>(box, infoInputTextField, label); } - public static Tuple3 getNonEditableValueBox() { + public static Tuple3 getNonEditableValueBox() { final Tuple3 editableValueBox = getEditableValueBox(""); - final TextField textField = editableValueBox.second; + final InputTextField textField = editableValueBox.second; textField.setDisable(true);