mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 09:52:23 +01:00
Merge branch 'master' of github.com:bisq-network/bisq into hotfix/v1.1.6
# Conflicts: # build.gradle # desktop/package/linux/Dockerfile # desktop/package/linux/package.sh # desktop/package/linux/release.sh # desktop/package/macosx/create_app.sh # desktop/package/macosx/finalize.sh # desktop/package/macosx/insert_snapshot_version.sh # desktop/package/windows/package.bat # desktop/package/windows/release.bat # relay/src/main/resources/version.txt
This commit is contained in:
commit
4201e065bc
@ -19,6 +19,7 @@ All Bisq contributors submit changes via pull requests. The workflow is as follo
|
||||
- Fork the repository
|
||||
- Create a topic branch from the `master` branch
|
||||
- Commit patches
|
||||
- Squash redundant or unnecessary commits
|
||||
- Submit a pull request from your topic branch back to the `master` branch of the main repository
|
||||
|
||||
Pull requests should be focused on a single change. Do not mix, for example, refactorings with a bug fix or implementation of a new feature. This practice makes it easier for fellow contributors to review each pull request on its merits and and to give a clear ACK/NACK (see below).
|
||||
@ -73,6 +74,10 @@ https://help.github.com/articles/signing-commits-with-gpg/ for instructions.
|
||||
|
||||
The [.editorconfig](.editorconfig) settings in this repository ensure consistent management of whitespace, line endings and more. Most modern editors support it natively or with plugin. See http://editorconfig.org for details. See also [bisq-network/style#10](https://github.com/bisq-network/style/issues/10).
|
||||
|
||||
### Keep the git history clean
|
||||
|
||||
It's very important to keep the git history clear, light and easily browsable. This means contributors must make sure their pull requests include only meaningful commits (if they are redundant or were added after a review, they should be removed) and _no merge commits_.
|
||||
|
||||
### Additional style guidelines
|
||||
|
||||
See the issues in the [bisq-network/style](https://github.com/bisq-network/style/issues) repository.
|
||||
|
@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.asset.coins;
|
||||
|
||||
import bisq.asset.AddressValidationResult;
|
||||
import bisq.asset.AddressValidator;
|
||||
import bisq.asset.Coin;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bitcoinj.core.Base58;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
|
||||
public class Ergo extends Coin {
|
||||
|
||||
public Ergo() {
|
||||
super("Ergo", "ERG", new ErgoAddressValidator());
|
||||
}
|
||||
|
||||
public static class ErgoAddressValidator implements AddressValidator {
|
||||
|
||||
@Override
|
||||
public AddressValidationResult validate(String address) {
|
||||
try {
|
||||
byte[] decoded = Base58.decode(address);
|
||||
if (decoded.length < 4) {
|
||||
return AddressValidationResult.invalidAddress("Input too short: " + decoded.length);
|
||||
}
|
||||
if (decoded[0] != 1 && decoded[0] != 2 && decoded[0] != 3) {
|
||||
return AddressValidationResult.invalidAddress("Invalid prefix");
|
||||
}
|
||||
byte[] data = Arrays.copyOfRange(decoded, 0, decoded.length - 4);
|
||||
byte[] checksum = Arrays.copyOfRange(decoded, decoded.length - 4, decoded.length);
|
||||
byte[] hashed = new byte[32];
|
||||
{
|
||||
Blake2bDigest digest = new Blake2bDigest(256);
|
||||
digest.update(data, 0, data.length);
|
||||
digest.doFinal(hashed, 0);
|
||||
}
|
||||
byte[] actualChecksum = Arrays.copyOfRange(hashed, 0, 4);
|
||||
if (!Arrays.equals(checksum, actualChecksum)) {
|
||||
return AddressValidationResult.invalidAddress("Invalid checksum");
|
||||
}
|
||||
} catch (AddressFormatException e) {
|
||||
return AddressValidationResult.invalidAddress(e);
|
||||
}
|
||||
return AddressValidationResult.validAddress();
|
||||
}
|
||||
}
|
||||
}
|
27
assets/src/main/java/bisq/asset/coins/NoteBlockchain.java
Normal file
27
assets/src/main/java/bisq/asset/coins/NoteBlockchain.java
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.asset.coins;
|
||||
|
||||
import bisq.asset.Coin;
|
||||
import bisq.asset.RegexAddressValidator;
|
||||
|
||||
public class NoteBlockchain extends Coin {
|
||||
public NoteBlockchain() {
|
||||
super("NoteBlockchain", "NTBC", new RegexAddressValidator("^[N][a-km-zA-HJ-NP-Z1-9]{26,33}$"));
|
||||
}
|
||||
}
|
@ -40,7 +40,6 @@ bisq.asset.coins.Donu
|
||||
bisq.asset.coins.Dragonglass
|
||||
bisq.asset.coins.DSTRA
|
||||
bisq.asset.coins.Emercoin
|
||||
bisq.asset.coins.Ergo
|
||||
bisq.asset.coins.Ether
|
||||
bisq.asset.coins.EtherClassic
|
||||
bisq.asset.coins.FourtyTwo
|
||||
@ -73,6 +72,7 @@ bisq.asset.coins.Myce
|
||||
bisq.asset.coins.Namecoin
|
||||
bisq.asset.coins.Navcoin
|
||||
bisq.asset.coins.Noir
|
||||
bisq.asset.coins.NoteBlockchain
|
||||
bisq.asset.coins.ParsiCoin
|
||||
bisq.asset.coins.Particl
|
||||
bisq.asset.coins.PENG
|
||||
|
@ -21,28 +21,23 @@ import bisq.asset.AbstractAssetTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ErgoTest extends AbstractAssetTest {
|
||||
public class NoteBlockchainTest extends AbstractAssetTest {
|
||||
|
||||
public ErgoTest() {
|
||||
super(new Ergo());
|
||||
public NoteBlockchainTest() {
|
||||
super(new NoteBlockchain());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidAddresses() {
|
||||
assertValidAddress("9fRAWhdxEsTcdb8PhGNrZfwqa65zfkuYHAMmkQLcic1gdLSV5vA");
|
||||
assertValidAddress("25qGdVWg2yyYho8uC1pLtc7KxFn4nEEAwD");
|
||||
assertValidAddress("23NL9a8ngN28ovtLiKLgHexcdTKBbUMLhH");
|
||||
assertValidAddress("7bwdkU5V8");
|
||||
assertValidAddress("BxKBaHkvrTvLZrDcZjcsxsF7aSsrN73ijeFZXtbj4CXZHHcvBtqSxQ");
|
||||
assertValidAddress("NaeSp6oTDFiGBZejFyYJvuCaSqWMnMM44E");
|
||||
assertValidAddress("NPCz6bsSnksLUGbp11hbHFWqFuVweEgMWM");
|
||||
assertValidAddress("NMNA6oMBExWhYoVEcD2BbcL6qmQ6rs7GN2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidAddresses() {
|
||||
assertInvalidAddress("9fRAWhdxEsTcdb8PhGNrZfwqa65zfkuYHAMmkQLcic1gdLSV5vAaa");
|
||||
assertInvalidAddress("afRAWhdxEsTcdb8PhGNrZfwqa65zfkuYHAMmkQLcic1gdLSV5vA");
|
||||
assertInvalidAddress("25qGdVWg2yyYho8uC1pLtc7KxFn4nEEAwDaa");
|
||||
assertInvalidAddress("23NL9a8ngN28ovtLiKLgHexcdTKBbUMLhHaa");
|
||||
assertInvalidAddress("7bwdkU5V8aa");
|
||||
assertInvalidAddress("BxKBaHkvrTvLZrDcZjcsxsF7aSsrN73ijeFZXtbj4CXZHHcvBtqSxQ#");
|
||||
assertInvalidAddress("1NMNA6oMBExWhYoVEcD2BbcL6qmQ6rs7GN2");
|
||||
assertInvalidAddress("NMNA6oMBExyWhYoVEcD2BbcL6qmQ6rs7GN2");
|
||||
assertInvalidAddress("NMNA6oMBExWhYoVEcD2BbcL6qmQ6rs7GN2#");
|
||||
}
|
||||
}
|
12
build.gradle
12
build.gradle
@ -33,7 +33,6 @@ configure(subprojects) {
|
||||
bitcoinjVersion = 'a88d36d'
|
||||
btcdCli4jVersion = '975ff5d4'
|
||||
codecVersion = '1.9'
|
||||
controlsfxVersion = '8.0.6_20'
|
||||
easybindVersion = '1.0.3'
|
||||
easyVersion = '4.0.1'
|
||||
findbugsVersion = '3.0.2'
|
||||
@ -60,11 +59,10 @@ configure(subprojects) {
|
||||
logbackVersion = '1.1.10'
|
||||
lombokVersion = '1.18.2'
|
||||
mockitoVersion = '3.0.0'
|
||||
netlayerVersion = '0.6.5.1'
|
||||
netlayerVersion = '0.6.5.2'
|
||||
protobufVersion = '3.9.1'
|
||||
pushyVersion = '0.13.2'
|
||||
qrgenVersion = '1.3'
|
||||
reactfxVersion = '2.0-M3'
|
||||
sarxosVersion = '0.3.12'
|
||||
slf4jVersion = '1.7.22'
|
||||
sparkVersion = '2.5.2'
|
||||
@ -158,7 +156,6 @@ configure(project(':assets')) {
|
||||
compile "com.google.guava:guava:$guavaVersion"
|
||||
compile "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
compile "org.apache.commons:commons-lang3:$langVersion"
|
||||
compile "org.bouncycastle:bcpg-jdk15on:$bcVersion"
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,9 +177,6 @@ configure(project(':common')) {
|
||||
compile "org.openjfx:javafx-graphics:11:$os"
|
||||
compile "com.google.protobuf:protobuf-java:$protobufVersion"
|
||||
compile 'com.google.code.gson:gson:2.7'
|
||||
compile("com.googlecode.json-simple:json-simple:$jsonsimpleVersion") {
|
||||
exclude(module: 'junit')
|
||||
}
|
||||
compile "org.springframework:spring-core:$springVersion"
|
||||
compile "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
compile "ch.qos.logback:logback-core:$logbackVersion"
|
||||
@ -199,7 +193,6 @@ configure(project(':common')) {
|
||||
exclude(module: 'protobuf-java')
|
||||
}
|
||||
compile "org.jetbrains:annotations:$jetbrainsAnnotationsVersion"
|
||||
runtime "org.bouncycastle:bcprov-jdk15on:$bcVersion"
|
||||
compile "org.bouncycastle:bcpg-jdk15on:$bcVersion"
|
||||
compile "commons-io:commons-io:$ioVersion"
|
||||
compile "org.apache.commons:commons-lang3:$langVersion"
|
||||
@ -294,8 +287,6 @@ configure(project(':desktop')) {
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile "org.controlsfx:controlsfx:$controlsfxVersion"
|
||||
compile "org.reactfx:reactfx:$reactfxVersion"
|
||||
compile "net.glxn:qrgen:$qrgenVersion"
|
||||
compile "de.jensd:fontawesomefx:$fontawesomefxVersion"
|
||||
compile "de.jensd:fontawesomefx-commons:$fontawesomefxCommonsVersion"
|
||||
@ -396,7 +387,6 @@ configure(project(':seednode')) {
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
runtime "org.bouncycastle:bcprov-jdk15on:$bcVersion"
|
||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||
testCompile "org.springframework:spring-test:$springVersion"
|
||||
|
@ -54,8 +54,7 @@ public class FrameRateTimer implements Timer, Runnable {
|
||||
stop();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
log.error(t.getMessage());
|
||||
t.printStackTrace();
|
||||
log.error("exception in FrameRateTimer", t);
|
||||
stop();
|
||||
throw t;
|
||||
}
|
||||
|
@ -93,8 +93,7 @@ public class UserThread {
|
||||
return timerClass.getDeclaredConstructor().newInstance();
|
||||
} catch (InstantiationException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||
String message = "Could not instantiate timer bsTimerClass=" + timerClass;
|
||||
log.error(message);
|
||||
e.printStackTrace();
|
||||
log.error(message, e);
|
||||
throw new RuntimeException(message);
|
||||
}
|
||||
}
|
||||
|
@ -38,4 +38,3 @@ public class CryptoUtils {
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,8 +18,7 @@
|
||||
package bisq.common.crypto;
|
||||
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import bisq.common.util.Hex;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
@ -70,8 +69,7 @@ public class Encryption {
|
||||
log.trace("Generate msgEncryptionKeyPair needed {} ms", System.currentTimeMillis() - ts);
|
||||
return keyPair;
|
||||
} catch (Throwable e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
log.error("Could not create key.", e);
|
||||
throw new RuntimeException("Could not create key.");
|
||||
}
|
||||
}
|
||||
@ -87,7 +85,7 @@ public class Encryption {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||
return cipher.doFinal(payload);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
log.error("error in encrypt", e);
|
||||
throw new CryptoException(e);
|
||||
}
|
||||
}
|
||||
@ -128,8 +126,7 @@ public class Encryption {
|
||||
outputStream.flush();
|
||||
payloadWithHmac = outputStream.toByteArray().clone();
|
||||
} catch (IOException | NoSuchProviderException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
log.error("Could not create hmac", e);
|
||||
throw new RuntimeException("Could not create hmac");
|
||||
} finally {
|
||||
if (outputStream != null) {
|
||||
@ -140,8 +137,7 @@ public class Encryption {
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
log.error("Could not create hmac", e);
|
||||
throw new RuntimeException("Could not create hmac");
|
||||
}
|
||||
return payloadWithHmac;
|
||||
@ -153,8 +149,7 @@ public class Encryption {
|
||||
byte[] hmacTest = getHmac(message, secretKey);
|
||||
return Arrays.equals(hmacTest, hmac);
|
||||
} catch (Throwable e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
log.error("Could not create cipher", e);
|
||||
throw new RuntimeException("Could not create cipher");
|
||||
}
|
||||
}
|
||||
@ -177,7 +172,7 @@ public class Encryption {
|
||||
|
||||
public static byte[] decryptPayloadWithHmac(byte[] encryptedPayloadWithHmac, SecretKey secretKey) throws CryptoException {
|
||||
byte[] payloadWithHmac = decrypt(encryptedPayloadWithHmac, secretKey);
|
||||
String payloadWithHmacAsHex = Hex.toHexString(payloadWithHmac);
|
||||
String payloadWithHmacAsHex = Hex.encode(payloadWithHmac);
|
||||
// first part is raw message
|
||||
int length = payloadWithHmacAsHex.length();
|
||||
int sep = length - 64;
|
||||
@ -204,7 +199,7 @@ public class Encryption {
|
||||
cipher.init(Cipher.WRAP_MODE, publicKey, oaepParameterSpec);
|
||||
return cipher.wrap(secretKey);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
log.error("Couldn't encrypt payload", e);
|
||||
throw new CryptoException("Couldn't encrypt payload");
|
||||
}
|
||||
}
|
||||
@ -233,8 +228,7 @@ public class Encryption {
|
||||
keyPairGenerator.init(bits);
|
||||
return keyPairGenerator.generateKey();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
log.error(e.getMessage());
|
||||
log.error("Couldn't generate key", e);
|
||||
throw new RuntimeException("Couldn't generate key");
|
||||
}
|
||||
}
|
||||
@ -252,7 +246,6 @@ public class Encryption {
|
||||
return KeyFactory.getInstance(Encryption.ASYM_KEY_ALGO).generatePublic(new X509EncodedKeySpec(encryptionPubKeyBytes));
|
||||
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
|
||||
log.error("Error creating sigPublicKey from bytes. sigPublicKeyBytes as hex={}, error={}", Utilities.bytesAsHexString(encryptionPubKeyBytes), e);
|
||||
e.printStackTrace();
|
||||
throw new KeyConversionException(e);
|
||||
}
|
||||
}
|
||||
|
@ -43,8 +43,7 @@ public class Hash {
|
||||
digest.update(data, 0, data.length);
|
||||
return digest.digest();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
log.error("Could not create MessageDigest for hash. " + e.toString());
|
||||
e.printStackTrace();
|
||||
log.error("Could not create MessageDigest for hash. ", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
@ -20,18 +20,12 @@ package bisq.common.crypto;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
import java.security.KeyPair;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@Slf4j
|
||||
@ -41,33 +35,18 @@ public final class KeyRing {
|
||||
private final KeyPair encryptionKeyPair;
|
||||
private final PubKeyRing pubKeyRing;
|
||||
|
||||
// We generate by default a PGP keypair but the user can set his own if he prefers.
|
||||
// Not impl. yet but prepared in data structure
|
||||
@Nullable
|
||||
@Setter
|
||||
// TODO remove Nullable once impl.
|
||||
private PGPKeyPair pgpKeyPair;
|
||||
|
||||
@Inject
|
||||
public KeyRing(KeyStorage keyStorage) {
|
||||
if (keyStorage.allKeyFilesExist()) {
|
||||
signatureKeyPair = keyStorage.loadKeyPair(KeyStorage.KeyEntry.MSG_SIGNATURE);
|
||||
encryptionKeyPair = keyStorage.loadKeyPair(KeyStorage.KeyEntry.MSG_ENCRYPTION);
|
||||
|
||||
// TODO not impl
|
||||
pgpKeyPair = keyStorage.loadPgpKeyPair(KeyStorage.KeyEntry.PGP);
|
||||
} else {
|
||||
// First time we create key pairs
|
||||
signatureKeyPair = Sig.generateKeyPair();
|
||||
encryptionKeyPair = Encryption.generateKeyPair();
|
||||
|
||||
// TODO not impl
|
||||
pgpKeyPair = PGP.generateKeyPair();
|
||||
keyStorage.saveKeyRing(this);
|
||||
}
|
||||
// TODO remove Nullable once impl.
|
||||
final PGPPublicKey pgpPublicKey = pgpKeyPair != null ? pgpKeyPair.getPublicKey() : null;
|
||||
pubKeyRing = new PubKeyRing(signatureKeyPair.getPublic(), encryptionKeyPair.getPublic(), pgpPublicKey);
|
||||
pubKeyRing = new PubKeyRing(signatureKeyPair.getPublic(), encryptionKeyPair.getPublic());
|
||||
}
|
||||
|
||||
// Don't print keys for security reasons
|
||||
|
@ -24,8 +24,6 @@ import com.google.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
||||
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -54,8 +52,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
// TODO: use a password protection for key storage
|
||||
@Singleton
|
||||
public class KeyStorage {
|
||||
@ -65,9 +61,7 @@ public class KeyStorage {
|
||||
|
||||
public enum KeyEntry {
|
||||
MSG_SIGNATURE("sig", Sig.KEY_ALGO),
|
||||
MSG_ENCRYPTION("enc", Encryption.ASYM_KEY_ALGO),
|
||||
// TODO not impl
|
||||
PGP("pgp", null);
|
||||
MSG_ENCRYPTION("enc", Encryption.ASYM_KEY_ALGO);
|
||||
|
||||
private final String fileName;
|
||||
private final String algorithm;
|
||||
@ -111,14 +105,6 @@ public class KeyStorage {
|
||||
return new File(storageDir + "/" + keyEntry.getFileName() + ".key").exists();
|
||||
}
|
||||
|
||||
// TODO not impl
|
||||
@SuppressWarnings({"SameParameterValue", "SameReturnValue", "UnusedParameters"})
|
||||
@Nullable
|
||||
public PGPKeyPair loadPgpKeyPair(KeyEntry keyEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public KeyPair loadKeyPair(KeyEntry keyEntry) {
|
||||
FileUtil.rollingBackup(storageDir, keyEntry.getFileName() + ".key", 20);
|
||||
// long now = System.currentTimeMillis();
|
||||
@ -136,8 +122,7 @@ public class KeyStorage {
|
||||
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
|
||||
privateKey = keyFactory.generatePrivate(privateKeySpec);
|
||||
} catch (InvalidKeySpecException | IOException e) {
|
||||
log.error(e.getMessage());
|
||||
e.printStackTrace();
|
||||
log.error("Could not load key " + keyEntry.toString(), e.getMessage());
|
||||
throw new RuntimeException("Could not load key " + keyEntry.toString(), e);
|
||||
}
|
||||
|
||||
@ -161,8 +146,7 @@ public class KeyStorage {
|
||||
log.debug("load completed in {} msec", System.currentTimeMillis() - new Date().getTime());
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
e.printStackTrace();
|
||||
log.error(e.getMessage());
|
||||
log.error("Could not load key " + keyEntry.toString(), e);
|
||||
throw new RuntimeException("Could not load key " + keyEntry.toString(), e);
|
||||
}
|
||||
}
|
||||
@ -181,8 +165,7 @@ public class KeyStorage {
|
||||
try (FileOutputStream fos = new FileOutputStream(storageDir + "/" + name + ".key")) {
|
||||
fos.write(pkcs8EncodedKeySpec.getEncoded());
|
||||
} catch (IOException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
log.error("Could not save key " + name, e);
|
||||
throw new RuntimeException("Could not save key " + name, e);
|
||||
}
|
||||
}
|
||||
|
@ -1,133 +0,0 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.common.crypto;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import org.bouncycastle.bcpg.BCPGKey;
|
||||
import org.bouncycastle.bcpg.RSAPublicBCPGKey;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.jcajce.JcaPGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@SuppressWarnings("UnusedAssignment")
|
||||
@Slf4j
|
||||
public class PGP {
|
||||
|
||||
// TODO not tested yet, remove Nullable once impl.
|
||||
// PEM encoding
|
||||
@Nullable
|
||||
public static PGPPublicKey getPubKeyFromPem(@Nullable String pem) {
|
||||
if (pem != null) {
|
||||
InputStream inputStream = new ByteArrayInputStream(pem.getBytes(Charsets.UTF_8));
|
||||
try {
|
||||
inputStream = PGPUtil.getDecoderStream(inputStream);
|
||||
try {
|
||||
JcaPGPPublicKeyRingCollection ringCollection = new JcaPGPPublicKeyRingCollection(inputStream);
|
||||
Iterator<PGPPublicKeyRing> keyRingsIterator = ringCollection.getKeyRings();
|
||||
while (keyRingsIterator.hasNext()) {
|
||||
PGPPublicKeyRing pgpPublicKeyRing = keyRingsIterator.next();
|
||||
Iterator<PGPPublicKey> pubKeysIterator = pgpPublicKeyRing.getPublicKeys();
|
||||
while (pubKeysIterator.hasNext()) {
|
||||
final PGPPublicKey pgpPublicKey = pubKeysIterator.next();
|
||||
if ((pgpPublicKey).isEncryptionKey()) {
|
||||
log.debug(pgpPublicKey.getClass().getName()
|
||||
+ " KeyID: " + Long.toHexString(pgpPublicKey.getKeyID())
|
||||
+ " type: " + pgpPublicKey.getAlgorithm()
|
||||
+ " fingerprint: " + new String(Hex.encode(pgpPublicKey.getFingerprint())));
|
||||
|
||||
BCPGKey bcKey = pgpPublicKey.getPublicKeyPacket().getKey();
|
||||
log.debug(bcKey.getClass().getName());
|
||||
if (bcKey instanceof RSAPublicBCPGKey) {
|
||||
RSAPublicBCPGKey bcRSA = (RSAPublicBCPGKey) bcKey;
|
||||
RSAPublicKeySpec specRSA = new RSAPublicKeySpec(bcRSA.getModulus(), bcRSA.getPublicExponent());
|
||||
PublicKey jceKey = KeyFactory.getInstance("RSA").generatePublic(specRSA);
|
||||
// if you want to use the key in JCE, use jceKey
|
||||
// if you want to write "X.509" (SPKI) DER format to a file:
|
||||
//Files.write(new File(pubKeyAsString).toPath(), jceKey.getEncoded());
|
||||
// if you want to write in PEM, bouncycastle can do that
|
||||
// or you can just do base64 and add BEGIN/END lines
|
||||
// return pubKeyAsString; // assume only one key; if need to handle multiple keys
|
||||
// or select other than the first, specify more clearly
|
||||
}
|
||||
|
||||
return pgpPublicKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (PGPException | InvalidKeySpecException | NoSuchAlgorithmException e) {
|
||||
log.error("Error creating publicKey from pem. pem={}, error={}", pem, e);
|
||||
e.printStackTrace();
|
||||
throw new KeyConversionException(e);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("Error creating publicKey from pem. pem={}, error={}", pem, e);
|
||||
e.printStackTrace();
|
||||
throw new KeyConversionException(e);
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("Error creating publicKey from pem. pem=null");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO not impl, remove Nullable once impl.
|
||||
// PEM encoding
|
||||
@SuppressWarnings({"SameReturnValue", "UnusedParameters"})
|
||||
@NotNull
|
||||
public static String getPEMFromPubKey(@Nullable PGPPublicKey pgpPubKey) {
|
||||
// We use empty string as we must not have null in proto file
|
||||
return "";
|
||||
}
|
||||
|
||||
// TODO not impl, remove Nullable once impl.
|
||||
@SuppressWarnings("SameReturnValue")
|
||||
@Nullable
|
||||
public static PGPKeyPair generateKeyPair() {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -25,16 +25,12 @@ import com.google.protobuf.ByteString;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Same as KeyRing but with public keys only.
|
||||
* Used to send public keys over the wire to other peer.
|
||||
@ -45,22 +41,15 @@ import javax.annotation.Nullable;
|
||||
public final class PubKeyRing implements NetworkPayload, UsedForTradeContractJson {
|
||||
private final byte[] signaturePubKeyBytes;
|
||||
private final byte[] encryptionPubKeyBytes;
|
||||
@Nullable
|
||||
private final String pgpPubKeyAsPem;
|
||||
|
||||
private transient PublicKey signaturePubKey;
|
||||
private transient PublicKey encryptionPubKey;
|
||||
@Nullable
|
||||
private transient PGPPublicKey pgpPubKey;
|
||||
|
||||
public PubKeyRing(PublicKey signaturePubKey, PublicKey encryptionPubKey, @Nullable PGPPublicKey pgpPubKey) {
|
||||
public PubKeyRing(PublicKey signaturePubKey, PublicKey encryptionPubKey) {
|
||||
this.signaturePubKeyBytes = Sig.getPublicKeyBytes(signaturePubKey);
|
||||
this.encryptionPubKeyBytes = Encryption.getPublicKeyBytes(encryptionPubKey);
|
||||
this.pgpPubKeyAsPem = PGP.getPEMFromPubKey(pgpPubKey);
|
||||
|
||||
this.signaturePubKey = signaturePubKey;
|
||||
this.encryptionPubKey = encryptionPubKey;
|
||||
this.pgpPubKey = pgpPubKey;
|
||||
}
|
||||
|
||||
|
||||
@ -69,15 +58,11 @@ public final class PubKeyRing implements NetworkPayload, UsedForTradeContractJso
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@VisibleForTesting
|
||||
public PubKeyRing(byte[] signaturePubKeyBytes, byte[] encryptionPubKeyBytes, @Nullable String pgpPubKeyAsPem) {
|
||||
public PubKeyRing(byte[] signaturePubKeyBytes, byte[] encryptionPubKeyBytes) {
|
||||
this.signaturePubKeyBytes = signaturePubKeyBytes;
|
||||
this.encryptionPubKeyBytes = encryptionPubKeyBytes;
|
||||
this.pgpPubKeyAsPem = pgpPubKeyAsPem;
|
||||
|
||||
signaturePubKey = Sig.getPublicKeyFromBytes(signaturePubKeyBytes);
|
||||
encryptionPubKey = Encryption.getPublicKeyFromBytes(encryptionPubKeyBytes);
|
||||
if (pgpPubKeyAsPem != null)
|
||||
pgpPubKey = PGP.getPubKeyFromPem(pgpPubKeyAsPem);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -85,14 +70,13 @@ public final class PubKeyRing implements NetworkPayload, UsedForTradeContractJso
|
||||
return protobuf.PubKeyRing.newBuilder()
|
||||
.setSignaturePubKeyBytes(ByteString.copyFrom(signaturePubKeyBytes))
|
||||
.setEncryptionPubKeyBytes(ByteString.copyFrom(encryptionPubKeyBytes))
|
||||
.setPgpPubKeyAsPem(pgpPubKeyAsPem)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static PubKeyRing fromProto(protobuf.PubKeyRing proto) {
|
||||
return new PubKeyRing(proto.getSignaturePubKeyBytes().toByteArray(),
|
||||
proto.getEncryptionPubKeyBytes().toByteArray(),
|
||||
proto.getPgpPubKeyAsPem());
|
||||
return new PubKeyRing(
|
||||
proto.getSignaturePubKeyBytes().toByteArray(),
|
||||
proto.getEncryptionPubKeyBytes().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -100,7 +84,6 @@ public final class PubKeyRing implements NetworkPayload, UsedForTradeContractJso
|
||||
return "PubKeyRing{" +
|
||||
"signaturePubKeyHex=" + Utilities.bytesAsHexString(signaturePubKeyBytes) +
|
||||
", encryptionPubKeyHex=" + Utilities.bytesAsHexString(encryptionPubKeyBytes) +
|
||||
", pgpPubKeyAsString=" + pgpPubKeyAsPem +
|
||||
'}';
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,10 @@
|
||||
package bisq.common.crypto;
|
||||
|
||||
import bisq.common.util.Utilities;
|
||||
import bisq.common.util.Base64;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
@ -65,8 +64,7 @@ public class Sig {
|
||||
log.trace("Generate msgSignatureKeyPair needed {} ms", System.currentTimeMillis() - ts);
|
||||
return keyPair;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
log.error(e.toString());
|
||||
log.error("Could not create key.", e);
|
||||
throw new RuntimeException("Could not create key.");
|
||||
}
|
||||
}
|
||||
@ -95,7 +93,7 @@ public class Sig {
|
||||
*/
|
||||
public static String sign(PrivateKey privateKey, String message) throws CryptoException {
|
||||
byte[] sigAsBytes = sign(privateKey, message.getBytes(Charsets.UTF_8));
|
||||
return Base64.toBase64String(sigAsBytes);
|
||||
return Base64.encode(sigAsBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -143,4 +141,3 @@ public class Sig {
|
||||
return new X509EncodedKeySpec(sigPublicKey.getEncoded()).getEncoded();
|
||||
}
|
||||
}
|
||||
|
||||
|
104
common/src/main/java/bisq/common/reactfx/FxTimer.java
Normal file
104
common/src/main/java/bisq/common/reactfx/FxTimer.java
Normal file
@ -0,0 +1,104 @@
|
||||
package bisq.common.reactfx;
|
||||
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.util.Duration;
|
||||
|
||||
/**
|
||||
* Provides factory methods for timers that are manipulated from and execute
|
||||
* their action on the JavaFX application thread.
|
||||
*
|
||||
* Copied from:
|
||||
* https://github.com/TomasMikula/ReactFX/blob/537fffdbb2958a77dfbca08b712bb2192862e960/reactfx/src/main/java/org/reactfx/util/FxTimer.java
|
||||
*
|
||||
*/
|
||||
public class FxTimer implements Timer {
|
||||
|
||||
/**
|
||||
* Prepares a (stopped) timer that lasts for {@code delay} and whose action runs when timer <em>ends</em>.
|
||||
*/
|
||||
public static Timer create(java.time.Duration delay, Runnable action) {
|
||||
return new FxTimer(delay, delay, action, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@code create(delay, action).restart()}.
|
||||
*/
|
||||
public static Timer runLater(java.time.Duration delay, Runnable action) {
|
||||
Timer timer = create(delay, action);
|
||||
timer.restart();
|
||||
return timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a (stopped) timer that lasts for {@code interval} and that executes the given action periodically
|
||||
* when the timer <em>ends</em>.
|
||||
*/
|
||||
public static Timer createPeriodic(java.time.Duration interval, Runnable action) {
|
||||
return new FxTimer(interval, interval, action, Animation.INDEFINITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@code createPeriodic(interval, action).restart()}.
|
||||
*/
|
||||
public static Timer runPeriodically(java.time.Duration interval, Runnable action) {
|
||||
Timer timer = createPeriodic(interval, action);
|
||||
timer.restart();
|
||||
return timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a (stopped) timer that lasts for {@code interval} and that executes the given action periodically
|
||||
* when the timer <em>starts</em>.
|
||||
*/
|
||||
public static Timer createPeriodic0(java.time.Duration interval, Runnable action) {
|
||||
return new FxTimer(java.time.Duration.ZERO, interval, action, Animation.INDEFINITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@code createPeriodic0(interval, action).restart()}.
|
||||
*/
|
||||
public static Timer runPeriodically0(java.time.Duration interval, Runnable action) {
|
||||
Timer timer = createPeriodic0(interval, action);
|
||||
timer.restart();
|
||||
return timer;
|
||||
}
|
||||
|
||||
private final Duration actionTime;
|
||||
private final Timeline timeline;
|
||||
private final Runnable action;
|
||||
|
||||
private long seq = 0;
|
||||
|
||||
private FxTimer(java.time.Duration actionTime, java.time.Duration period, Runnable action, int cycles) {
|
||||
this.actionTime = Duration.millis(actionTime.toMillis());
|
||||
this.timeline = new Timeline();
|
||||
this.action = action;
|
||||
|
||||
timeline.getKeyFrames().add(new KeyFrame(this.actionTime)); // used as placeholder
|
||||
if (period != actionTime) {
|
||||
timeline.getKeyFrames().add(new KeyFrame(Duration.millis(period.toMillis())));
|
||||
}
|
||||
|
||||
timeline.setCycleCount(cycles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restart() {
|
||||
stop();
|
||||
long expected = seq;
|
||||
timeline.getKeyFrames().set(0, new KeyFrame(actionTime, ae -> {
|
||||
if(seq == expected) {
|
||||
action.run();
|
||||
}
|
||||
}));
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
timeline.stop();
|
||||
++seq;
|
||||
}
|
||||
}
|
10
common/src/main/java/bisq/common/reactfx/LICENSE
Normal file
10
common/src/main/java/bisq/common/reactfx/LICENSE
Normal file
@ -0,0 +1,10 @@
|
||||
Copyright (c) 2013-2014, Tomas Mikula
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
6
common/src/main/java/bisq/common/reactfx/README.md
Normal file
6
common/src/main/java/bisq/common/reactfx/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
This package is a very minimal subset of the external library `org.reactfx`.
|
||||
|
||||
Two small files from `org.reactfx` were embedded into the project
|
||||
to avoid having it as dependency:
|
||||
|
||||
[https://github.com/TomasMikula/ReactFX]
|
66
common/src/main/java/bisq/common/reactfx/Timer.java
Normal file
66
common/src/main/java/bisq/common/reactfx/Timer.java
Normal file
@ -0,0 +1,66 @@
|
||||
package bisq.common.reactfx;
|
||||
|
||||
/**
|
||||
* Timer represents a delayed action. This means that every timer has an
|
||||
* associated action and an associated delay. Action and delay are specified
|
||||
* on timer creation.
|
||||
*
|
||||
* <p>Every timer also has an associated thread (such as JavaFX application
|
||||
* thread or a single-thread executor's thread). Timer may only be accessed
|
||||
* from its associated thread. Timer's action is executed on its associated
|
||||
* thread, too. This design allows to implement guarantees provided by
|
||||
* {@link #stop()}.
|
||||
*
|
||||
* Copied from:
|
||||
* https://raw.githubusercontent.com/TomasMikula/ReactFX/537fffdbb2958a77dfbca08b712bb2192862e960/reactfx/src/main/java/org/reactfx/util/Timer.java*
|
||||
*/
|
||||
public interface Timer {
|
||||
/**
|
||||
* Schedules the associated action to be executed after the associated
|
||||
* delay. If the action is already scheduled but hasn't been executed yet,
|
||||
* the timeout is reset, so that the action won't be executed before the
|
||||
* full delay from now.
|
||||
*/
|
||||
void restart();
|
||||
|
||||
/**
|
||||
* If the associated action has been scheduled for execution but not yet
|
||||
* executed, this method prevents it from being executed at all. This is
|
||||
* also true in case the timer's timeout has already expired, but the
|
||||
* associated action hasn't had a chance to be executed on the associated
|
||||
* thread. Note that this is a stronger guarantee than the one given by
|
||||
* {@link javafx.animation.Animation#stop()}:
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* Timeline timeline = new Timeline(new KeyFrame(
|
||||
* Duration.millis(1000),
|
||||
* ae -> System.out.println("FIRED ANYWAY")));
|
||||
* timeline.play();
|
||||
*
|
||||
* // later on the JavaFX application thread,
|
||||
* // but still before the action has been executed
|
||||
* timeline.stop();
|
||||
*
|
||||
* // later, "FIRED ANYWAY" may still be printed
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* In contrast, using the {@link FxTimer}, the action is guaranteed not to
|
||||
* be executed after {@code stop()}:
|
||||
* <pre>
|
||||
* {@code
|
||||
* Timer timer = FxTimer.runLater(
|
||||
* Duration.ofMillis(1000),
|
||||
* () -> System.out.println("FIRED"));
|
||||
*
|
||||
* // later on the JavaFX application thread,
|
||||
* // but still before the action has been executed
|
||||
* timer.stop();
|
||||
*
|
||||
* // "FIRED" is guaranteed *not* to be printed
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
void stop();
|
||||
}
|
@ -20,14 +20,9 @@ package bisq.common.storage;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
@ -102,15 +97,4 @@ public class JsonFileManager {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Object readJsonFromDisc(String fileName) {
|
||||
final File jsonFile = new File(Paths.get(dir.getAbsolutePath(), fileName + ".json").toString());
|
||||
JSONParser parser = new JSONParser();
|
||||
try {
|
||||
return parser.parse(new FileReader(jsonFile));
|
||||
} catch (ParseException | IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
33
common/src/main/java/bisq/common/util/Base64.java
Normal file
33
common/src/main/java/bisq/common/util/Base64.java
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.common.util;
|
||||
|
||||
/**
|
||||
* We use Java 8 builtin Base64 because it is much faster than Guava and Apache versions:
|
||||
* http://java-performance.info/base64-encoding-and-decoding-performance/
|
||||
*/
|
||||
public class Base64 {
|
||||
|
||||
public static byte[] decode(String base64) {
|
||||
return java.util.Base64.getDecoder().decode(base64);
|
||||
}
|
||||
|
||||
public static String encode(byte[] bytes) {
|
||||
return java.util.Base64.getEncoder().encodeToString(bytes);
|
||||
}
|
||||
}
|
31
common/src/main/java/bisq/common/util/Hex.java
Normal file
31
common/src/main/java/bisq/common/util/Hex.java
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.common.util;
|
||||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
|
||||
public class Hex {
|
||||
|
||||
public static byte[] decode(String hex) {
|
||||
return BaseEncoding.base16().lowerCase().decode(hex.toLowerCase());
|
||||
}
|
||||
|
||||
public static String encode(byte[] bytes) {
|
||||
return BaseEncoding.base16().lowerCase().encode(bytes);
|
||||
}
|
||||
}
|
@ -431,7 +431,7 @@ message Peer {
|
||||
message PubKeyRing {
|
||||
bytes signature_pub_key_bytes = 1;
|
||||
bytes encryption_pub_key_bytes = 2;
|
||||
string pgp_pub_key_as_pem = 3;
|
||||
reserved 3; // WAS: string pgp_pub_key_as_pem = 3;
|
||||
}
|
||||
|
||||
message SealedAndSigned {
|
||||
|
@ -70,9 +70,7 @@ public class AvoidStandbyModeService {
|
||||
private void start() {
|
||||
isStopped = false;
|
||||
log.info("AvoidStandbyModeService started");
|
||||
Thread thread = new Thread(this::play);
|
||||
thread.setName("AvoidStandbyModeService-thread");
|
||||
thread.start();
|
||||
new Thread(this::play, "AvoidStandbyModeService-thread").start();
|
||||
}
|
||||
|
||||
|
||||
|
@ -446,11 +446,8 @@ public class BisqSetup {
|
||||
bisqEnvironment.getIgnoreLocalBtcNode()) {
|
||||
step3();
|
||||
} else {
|
||||
Thread checkIfLocalHostNodeIsRunningThread = new Thread(() -> {
|
||||
Thread.currentThread().setName("checkIfLocalHostNodeIsRunningThread");
|
||||
Socket socket = null;
|
||||
try {
|
||||
socket = new Socket();
|
||||
new Thread(() -> {
|
||||
try (Socket socket = new Socket()) {
|
||||
socket.connect(new InetSocketAddress(InetAddresses.forString("127.0.0.1"),
|
||||
BisqEnvironment.getBaseCurrencyNetwork().getParameters().getPort()), 5000);
|
||||
log.info("Localhost Bitcoin node detected.");
|
||||
@ -460,16 +457,8 @@ public class BisqSetup {
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
UserThread.execute(BisqSetup.this::step3);
|
||||
} finally {
|
||||
if (socket != null) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
checkIfLocalHostNodeIsRunningThread.start();
|
||||
}, "checkIfLocalHostNodeIsRunningThread").start();
|
||||
}
|
||||
}
|
||||
|
||||
@ -486,9 +475,8 @@ public class BisqSetup {
|
||||
// If users compile themselves they might miss that step and then would get an exception in the trade.
|
||||
// To avoid that we add here at startup a sample encryption and signing to see if it don't causes an exception.
|
||||
// See: https://github.com/bisq-network/exchange/blob/master/doc/build.md#7-enable-unlimited-strength-for-cryptographic-keys
|
||||
Thread checkCryptoThread = new Thread(() -> {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.currentThread().setName("checkCryptoThread");
|
||||
// just use any simple dummy msg
|
||||
Ping payload = new Ping(1, 1);
|
||||
SealedAndSigned sealedAndSigned = EncryptionService.encryptHybridWithSignature(payload,
|
||||
@ -508,8 +496,7 @@ public class BisqSetup {
|
||||
if (cryptoSetupFailedHandler != null)
|
||||
cryptoSetupFailedHandler.accept(msg);
|
||||
}
|
||||
});
|
||||
checkCryptoThread.start();
|
||||
}, "checkCryptoThread").start();
|
||||
}
|
||||
|
||||
private void startP2pNetworkAndWallet() {
|
||||
|
27
core/src/main/java/bisq/core/app/OSXStandbyModeDisabler.java
Normal file
27
core/src/main/java/bisq/core/app/OSXStandbyModeDisabler.java
Normal file
@ -0,0 +1,27 @@
|
||||
package bisq.core.app;
|
||||
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class OSXStandbyModeDisabler {
|
||||
public void doIt() {
|
||||
if (!Utilities.isOSX()) {
|
||||
return;
|
||||
}
|
||||
long pid = ProcessHandle.current().pid();
|
||||
try {
|
||||
String[] params = {"/usr/bin/caffeinate", "-w", "" + pid};
|
||||
|
||||
// we only start the process. caffeinate blocks until we exit.
|
||||
new ProcessBuilder(params).start();
|
||||
log.info("disabled power management via " + String.join(" ", params));
|
||||
} catch (IOException e) {
|
||||
log.error("could not disable standby mode on osx", e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -49,11 +49,8 @@ public class SetupUtils {
|
||||
// If users compile themselves they might miss that step and then would get an exception in the trade.
|
||||
// To avoid that we add here at startup a sample encryption and signing to see if it don't causes an exception.
|
||||
// See: https://github.com/bisq-network/exchange/blob/master/doc/build.md#7-enable-unlimited-strength-for-cryptographic-keys
|
||||
Thread checkCryptoThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Thread checkCryptoThread = new Thread(() -> {
|
||||
try {
|
||||
Thread.currentThread().setName("checkCryptoThread");
|
||||
// just use any simple dummy msg
|
||||
Ping payload = new Ping(1, 1);
|
||||
SealedAndSigned sealedAndSigned = EncryptionService.encryptHybridWithSignature(payload,
|
||||
@ -74,15 +71,13 @@ public class SetupUtils {
|
||||
e.printStackTrace();
|
||||
errorHandler.accept(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, "checkCryptoThread");
|
||||
checkCryptoThread.start();
|
||||
}
|
||||
|
||||
public static BooleanProperty readFromResources(P2PDataStorage p2PDataStorage) {
|
||||
BooleanProperty result = new SimpleBooleanProperty();
|
||||
Thread thread = new Thread(() -> {
|
||||
Thread.currentThread().setName("readFromResourcesThread");
|
||||
new Thread(() -> {
|
||||
// Used to load different files per base currency (EntryMap_BTC_MAINNET, EntryMap_LTC,...)
|
||||
final BaseCurrencyNetwork baseCurrencyNetwork = BisqEnvironment.getBaseCurrencyNetwork();
|
||||
final String postFix = "_" + baseCurrencyNetwork.name();
|
||||
@ -90,8 +85,7 @@ public class SetupUtils {
|
||||
p2PDataStorage.readFromResources(postFix);
|
||||
log.info("readFromResources took {} ms", (new Date().getTime() - ts));
|
||||
UserThread.execute(() -> result.set(true));
|
||||
});
|
||||
thread.start();
|
||||
}, "readFromResourcesThread").start();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ public class WalletAppSetup {
|
||||
result = Res.get("mainView.footer.btcInfo",
|
||||
peers,
|
||||
Res.get("mainView.footer.btcInfo.synchronizingWith"),
|
||||
getBtcNetworkAsString() + ": " + formatter.formatToPercentWithSymbol(percentage));
|
||||
getBtcNetworkAsString() + ": " + BSFormatter.formatToPercentWithSymbol(percentage));
|
||||
} else {
|
||||
result = Res.get("mainView.footer.btcInfo",
|
||||
peers,
|
||||
|
@ -561,13 +561,12 @@ public class WalletConfig extends AbstractIdleService {
|
||||
|
||||
private void installShutdownHook() {
|
||||
if (autoStop) Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
Thread.currentThread().setName("ShutdownHook");
|
||||
try {
|
||||
WalletConfig.this.stopAsync();
|
||||
WalletConfig.this.awaitTerminated();
|
||||
} catch (Throwable ignore) {
|
||||
}
|
||||
}));
|
||||
}, "WalletConfig ShutdownHook"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -180,9 +180,9 @@ public class TradeWalletService {
|
||||
boolean doBroadcast,
|
||||
@Nullable TxBroadcaster.Callback callback)
|
||||
throws InsufficientMoneyException, AddressFormatException {
|
||||
log.debug("fundingAddress {}", fundingAddress.toString());
|
||||
log.debug("reservedForTradeAddress {}", reservedForTradeAddress.toString());
|
||||
log.debug("changeAddress {}", changeAddress.toString());
|
||||
log.debug("fundingAddress {}", fundingAddress);
|
||||
log.debug("reservedForTradeAddress {}", reservedForTradeAddress);
|
||||
log.debug("changeAddress {}", changeAddress);
|
||||
log.info("reservedFundsForOffer {}", reservedFundsForOffer.toPlainString());
|
||||
log.debug("useSavingsWallet {}", useSavingsWallet);
|
||||
log.info("tradingFee {}", tradingFee.toPlainString());
|
||||
@ -245,9 +245,9 @@ public class TradeWalletService {
|
||||
TransactionVerificationException, WalletException,
|
||||
InsufficientMoneyException, AddressFormatException {
|
||||
|
||||
log.debug("preparedBsqTx {}", preparedBsqTx.toString());
|
||||
log.debug("fundingAddress {}", fundingAddress.toString());
|
||||
log.debug("changeAddress {}", changeAddress.toString());
|
||||
log.debug("preparedBsqTx {}", preparedBsqTx);
|
||||
log.debug("fundingAddress {}", fundingAddress);
|
||||
log.debug("changeAddress {}", changeAddress);
|
||||
log.debug("reservedFundsForOffer {}", reservedFundsForOffer.toPlainString());
|
||||
log.debug("useSavingsWallet {}", useSavingsWallet);
|
||||
log.debug("txFee {}", txFee.toPlainString());
|
||||
@ -346,10 +346,12 @@ public class TradeWalletService {
|
||||
Coin txFee,
|
||||
Address takersAddress) throws
|
||||
TransactionVerificationException {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("takerCreatesDepositsTxInputs called");
|
||||
log.debug("inputAmount {}", inputAmount.toFriendlyString());
|
||||
log.debug("txFee {}", txFee.toFriendlyString());
|
||||
log.debug("takersAddress {}", takersAddress.toString());
|
||||
}
|
||||
|
||||
// We add the mining fee 2 times to the deposit tx:
|
||||
// 1. Will be spent when publishing the deposit tx (paid by buyer)
|
||||
@ -448,9 +450,9 @@ public class TradeWalletService {
|
||||
log.debug("takerChangeAddressString {}", takerChangeAddressString);
|
||||
log.debug("makerAddress {}", makerAddress);
|
||||
log.debug("makerChangeAddress {}", makerChangeAddress);
|
||||
log.debug("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.debug("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.debug("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
log.debug("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey));
|
||||
log.debug("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey));
|
||||
log.debug("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey));
|
||||
|
||||
checkArgument(!takerRawTransactionInputs.isEmpty());
|
||||
|
||||
|
@ -40,8 +40,8 @@ public class DaoUtil {
|
||||
long now = new Date().getTime();
|
||||
SimpleDateFormat dateFormatter = new SimpleDateFormat("dd MMM", Locale.getDefault());
|
||||
SimpleDateFormat timeFormatter = new SimpleDateFormat("HH:mm", Locale.getDefault());
|
||||
String startDateTime = formatter.formatDateTime(new Date(now + (start - height) * 10 * 60 * 1000L), dateFormatter, timeFormatter);
|
||||
String endDateTime = formatter.formatDateTime(new Date(now + (end - height) * 10 * 60 * 1000L), dateFormatter, timeFormatter);
|
||||
String startDateTime = BSFormatter.formatDateTime(new Date(now + (start - height) * 10 * 60 * 1000L), dateFormatter, timeFormatter);
|
||||
String endDateTime = BSFormatter.formatDateTime(new Date(now + (end - height) * 10 * 60 * 1000L), dateFormatter, timeFormatter);
|
||||
|
||||
return Res.get("dao.cycle.phaseDurationWithoutBlocks", start, end, startDateTime, endDateTime);
|
||||
}
|
||||
@ -53,9 +53,9 @@ public class DaoUtil {
|
||||
long now = new Date().getTime();
|
||||
SimpleDateFormat dateFormatter = new SimpleDateFormat("dd MMM", Locale.getDefault());
|
||||
SimpleDateFormat timeFormatter = new SimpleDateFormat("HH:mm", Locale.getDefault());
|
||||
String startDateTime = formatter.formatDateTime(new Date(now + (start - height) * 10 * 60 * 1000L), dateFormatter, timeFormatter);
|
||||
String endDateTime = formatter.formatDateTime(new Date(now + (end - height) * 10 * 60 * 1000L), dateFormatter, timeFormatter);
|
||||
String durationTime = formatter.formatDurationAsWords(duration * 10 * 60 * 1000, false, false);
|
||||
String startDateTime = BSFormatter.formatDateTime(new Date(now + (start - height) * 10 * 60 * 1000L), dateFormatter, timeFormatter);
|
||||
String endDateTime = BSFormatter.formatDateTime(new Date(now + (end - height) * 10 * 60 * 1000L), dateFormatter, timeFormatter);
|
||||
String durationTime = BSFormatter.formatDurationAsWords(duration * 10 * 60 * 1000, false, false);
|
||||
return Res.get("dao.cycle.phaseDuration", duration, durationTime, start, end, startDateTime, endDateTime);
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ import bisq.core.dao.state.model.governance.Issuance;
|
||||
import bisq.core.dao.state.model.governance.IssuanceType;
|
||||
import bisq.core.dao.state.model.governance.ParamChange;
|
||||
import bisq.core.util.BsqFormatter;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
@ -923,7 +924,7 @@ public class DaoStateService implements DaoSetupService {
|
||||
}
|
||||
|
||||
public double getParamValueAsPercentDouble(String paramValue) {
|
||||
return bsqFormatter.parsePercentStringToDouble(paramValue);
|
||||
return ParsingUtils.parsePercentStringToDouble(paramValue);
|
||||
}
|
||||
|
||||
public int getParamValueAsBlock(String paramValue) {
|
||||
|
@ -34,6 +34,7 @@ public class LanguageUtil {
|
||||
"el", // Greek
|
||||
"es", // Spanish
|
||||
"pt", // Portuguese
|
||||
"pt_BR", // Brazilian Portuguese
|
||||
"zh", // Chinese
|
||||
"ru", // Russian
|
||||
"fr", // French
|
||||
@ -84,6 +85,12 @@ public class LanguageUtil {
|
||||
*/
|
||||
);
|
||||
|
||||
private static final List<String> rtlLanguagesCodes = Arrays.asList(
|
||||
"fa", // Persian
|
||||
"ar", // Arabic
|
||||
"iw" // Hebrew
|
||||
);
|
||||
|
||||
public static List<String> getAllLanguageCodes() {
|
||||
List<Locale> allLocales = LocaleUtil.getAllLocales();
|
||||
|
||||
@ -118,11 +125,17 @@ public class LanguageUtil {
|
||||
// Serbia
|
||||
// shows it in russian by default
|
||||
return "Srpski";
|
||||
} else if (locale.getLanguage().equals("pt_br")) {
|
||||
return "português (Brasil)";
|
||||
} else {
|
||||
return locale.getDisplayName(locale);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDefaultLanguageRTL() {
|
||||
return rtlLanguagesCodes.contains(LanguageUtil.getDefaultLanguageLocaleAsCode());
|
||||
}
|
||||
|
||||
public static List<String> getUserLanguageCodes() {
|
||||
return userLanguageCodes;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
package bisq.core.monetary;
|
||||
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import org.bitcoinj.core.Monetary;
|
||||
import org.bitcoinj.utils.MonetaryFormat;
|
||||
@ -89,7 +89,7 @@ public final class Altcoin implements Monetary, Comparable<Altcoin> {
|
||||
* @throws IllegalArgumentException if you try to specify fractional satoshis, or a value out of range.
|
||||
*/
|
||||
public static Altcoin parseAltcoin(final String currencyCode, String input) {
|
||||
String cleaned = BSFormatter.convertCharsForNumber(input);
|
||||
String cleaned = ParsingUtils.convertCharsForNumber(input);
|
||||
try {
|
||||
long val = new BigDecimal(cleaned).movePointRight(SMALLEST_UNIT_EXPONENT)
|
||||
.toBigIntegerExact().longValue();
|
||||
|
@ -18,7 +18,7 @@
|
||||
package bisq.core.monetary;
|
||||
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Monetary;
|
||||
@ -58,7 +58,7 @@ public class Price extends MonetaryWrapper implements Comparable<Price> {
|
||||
* @return The parsed Price.
|
||||
*/
|
||||
public static Price parse(String currencyCode, String input) {
|
||||
String cleaned = BSFormatter.convertCharsForNumber(input);
|
||||
String cleaned = ParsingUtils.convertCharsForNumber(input);
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode))
|
||||
return new Price(Fiat.parseFiat(currencyCode, cleaned));
|
||||
else
|
||||
|
@ -18,7 +18,7 @@
|
||||
package bisq.core.monetary;
|
||||
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import org.bitcoinj.core.Monetary;
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
@ -36,7 +36,7 @@ public class Volume extends MonetaryWrapper implements Comparable<Volume> {
|
||||
}
|
||||
|
||||
public static Volume parse(String input, String currencyCode) {
|
||||
String cleaned = BSFormatter.convertCharsForNumber(input);
|
||||
String cleaned = ParsingUtils.convertCharsForNumber(input);
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode))
|
||||
return new Volume(Fiat.parseFiat(currencyCode, cleaned));
|
||||
else
|
||||
|
@ -180,9 +180,9 @@ public class MarketAlerts {
|
||||
ratio = Math.abs(ratio);
|
||||
String msg = Res.get("account.notifications.marketAlert.message.msg",
|
||||
direction,
|
||||
formatter.getCurrencyPair(currencyCode),
|
||||
formatter.formatPrice(offerPrice),
|
||||
formatter.formatToPercentWithSymbol(ratio / 10000d),
|
||||
BSFormatter.getCurrencyPair(currencyCode),
|
||||
BSFormatter.formatPrice(offerPrice),
|
||||
BSFormatter.formatToPercentWithSymbol(ratio / 10000d),
|
||||
marketDir,
|
||||
Res.get(offer.getPaymentMethod().getId()),
|
||||
shortOfferId);
|
||||
|
@ -70,8 +70,8 @@ public class PriceAlert {
|
||||
if (priceAsLong > filter.getHigh() || priceAsLong < filter.getLow()) {
|
||||
String msg = Res.get("account.notifications.priceAlert.message.msg",
|
||||
currencyName,
|
||||
formatter.formatMarketPrice(priceAsDouble, currencyCode),
|
||||
formatter.getCurrencyPair(currencyCode));
|
||||
BSFormatter.formatMarketPrice(priceAsDouble, currencyCode),
|
||||
BSFormatter.getCurrencyPair(currencyCode));
|
||||
MobileMessage message = new MobileMessage(Res.get("account.notifications.priceAlert.message.title", currencyName),
|
||||
msg,
|
||||
MobileMessageType.PRICE);
|
||||
|
@ -322,19 +322,6 @@ public class OfferUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFeeWithFiatAmount(Coin makerFeeAsCoin,
|
||||
Optional<Volume> optionalFeeInFiat,
|
||||
BSFormatter formatter) {
|
||||
String fee = makerFeeAsCoin != null ? formatter.formatCoinWithCode(makerFeeAsCoin) : Res.get("shared.na");
|
||||
String feeInFiatAsString;
|
||||
if (optionalFeeInFiat != null && optionalFeeInFiat.isPresent()) {
|
||||
feeInFiatAsString = formatter.formatVolumeWithCode(optionalFeeInFiat.get());
|
||||
} else {
|
||||
feeInFiatAsString = Res.get("shared.na");
|
||||
}
|
||||
return Res.get("feeOptionWindow.fee", fee, feeInFiatAsString);
|
||||
}
|
||||
|
||||
|
||||
public static Map<String, String> getExtraDataMap(AccountAgeWitnessService accountAgeWitnessService,
|
||||
ReferralIdService referralIdService,
|
||||
|
@ -44,7 +44,8 @@ public class ProvidersRepository {
|
||||
"http://xc3nh4juf2hshy7e.onion/", // @emzy
|
||||
"http://ceaanhbvluug4we6.onion/", // @mrosseel
|
||||
"http://44mgyoe2b6oqiytt.onion/", // @devinbileck
|
||||
"http://62nvujg5iou3vu3i.onion/" // @alexej996
|
||||
"http://62nvujg5iou3vu3i.onion/", // @alexej996
|
||||
"http://gztmprecgqjq64zh.onion/" // @wiz
|
||||
);
|
||||
|
||||
private final String providersFromProgramArgs;
|
||||
|
@ -48,7 +48,7 @@ public class ApplyFilter extends TradeTask {
|
||||
|
||||
FilterManager filterManager = processModel.getFilterManager();
|
||||
if (nodeAddress != null && filterManager.isNodeAddressBanned(nodeAddress)) {
|
||||
failed("Other trader is banned by his node address.\n" +
|
||||
failed("Other trader is banned by their node address.\n" +
|
||||
"tradingPeerNodeAddress=" + nodeAddress);
|
||||
} else if (filterManager.isOfferIdBanned(trade.getId())) {
|
||||
failed("Offer ID is banned.\n" +
|
||||
@ -60,7 +60,7 @@ public class ApplyFilter extends TradeTask {
|
||||
failed("Payment method is banned.\n" +
|
||||
"Payment method=" + trade.getOffer().getPaymentMethod().getId());
|
||||
} else if (filterManager.isPeersPaymentAccountDataAreBanned(paymentAccountPayload, appliedPaymentAccountFilter)) {
|
||||
failed("Other trader is banned by his trading account data.\n" +
|
||||
failed("Other trader is banned by their trading account data.\n" +
|
||||
"paymentAccountPayload=" + paymentAccountPayload.getPaymentDetails() + "\n" +
|
||||
"banFilter=" + appliedPaymentAccountFilter[0].toString());
|
||||
} else if (filterManager.requireUpdateToNewVersionForTrading()) {
|
||||
|
@ -39,11 +39,13 @@ public class SellerVerifiesPeersAccountAge extends TradeTask {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
boolean isTradeRisky = OfferRestrictions.isTradeRisky(trade);
|
||||
boolean isTradePeersAccountAgeImmature = AccountAgeRestrictions.isTradePeersAccountAgeImmature(
|
||||
processModel.getAccountAgeWitnessService(), trade);
|
||||
log.debug("SellerVerifiesPeersAccountAge isOfferRisky={} isTradePeersAccountAgeImmature={}",
|
||||
OfferRestrictions.isTradeRisky(trade), AccountAgeRestrictions.isTradePeersAccountAgeImmature(
|
||||
processModel.getAccountAgeWitnessService(), trade));
|
||||
if (OfferRestrictions.isTradeRisky(trade) &&
|
||||
AccountAgeRestrictions.isTradePeersAccountAgeImmature(processModel.getAccountAgeWitnessService(), trade)) {
|
||||
isTradeRisky, isTradePeersAccountAgeImmature);
|
||||
if (isTradeRisky &&
|
||||
isTradePeersAccountAgeImmature) {
|
||||
failed("Violation of security restrictions:\n" +
|
||||
" - The peer's account was created after March 1st 2019\n" +
|
||||
" - The trade amount is above 0.01 BTC\n" +
|
||||
|
@ -20,12 +20,9 @@ package bisq.core.util;
|
||||
import bisq.core.app.BisqEnvironment;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.GlobalSettings;
|
||||
import bisq.core.locale.LanguageUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
@ -55,6 +52,7 @@ import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -64,27 +62,24 @@ import org.jetbrains.annotations.NotNull;
|
||||
public class BSFormatter {
|
||||
public final static String RANGE_SEPARATOR = " - ";
|
||||
|
||||
protected boolean useMilliBit;
|
||||
protected int scale = 3;
|
||||
|
||||
// We don't support localized formatting. Format is always using "." as decimal mark and no grouping separator.
|
||||
// Input of "," as decimal mark (like in german locale) will be replaced with ".".
|
||||
// Input of a group separator (1,123,45) lead to an validation error.
|
||||
// Note: BtcFormat was intended to be used, but it lead to many problems (automatic format to mBit,
|
||||
// no way to remove grouping separator). It seems to be not optimal for user input formatting.
|
||||
protected MonetaryFormat coinFormat;
|
||||
@Getter
|
||||
protected MonetaryFormat monetaryFormat;
|
||||
|
||||
// protected String currencyCode = CurrencyUtil.getDefaultFiatCurrencyAsCode();
|
||||
|
||||
protected final MonetaryFormat fiatPriceFormat = new MonetaryFormat().shift(0).minDecimals(4).repeatOptionalDecimals(0, 0);
|
||||
protected final MonetaryFormat fiatVolumeFormat = new MonetaryFormat().shift(0).minDecimals(2).repeatOptionalDecimals(0, 0);
|
||||
protected final MonetaryFormat altcoinFormat = new MonetaryFormat().shift(0).minDecimals(8).repeatOptionalDecimals(0, 0);
|
||||
protected final DecimalFormat decimalFormat = new DecimalFormat("#.#");
|
||||
public static final MonetaryFormat fiatPriceFormat = new MonetaryFormat().shift(0).minDecimals(4).repeatOptionalDecimals(0, 0);
|
||||
protected static final MonetaryFormat altcoinFormat = new MonetaryFormat().shift(0).minDecimals(8).repeatOptionalDecimals(0, 0);
|
||||
protected static final DecimalFormat decimalFormat = new DecimalFormat("#.#");
|
||||
|
||||
|
||||
@Inject
|
||||
public BSFormatter() {
|
||||
coinFormat = BisqEnvironment.getParameters().getMonetaryFormat();
|
||||
monetaryFormat = BisqEnvironment.getParameters().getMonetaryFormat();
|
||||
}
|
||||
|
||||
|
||||
@ -106,10 +101,14 @@ public class BSFormatter {
|
||||
}
|
||||
|
||||
public String formatCoin(Coin coin, int decimalPlaces, boolean decimalAligned, int maxNumberOfDigits) {
|
||||
return formatCoin(coin, decimalPlaces, decimalAligned, maxNumberOfDigits, coinFormat);
|
||||
return formatCoin(coin, decimalPlaces, decimalAligned, maxNumberOfDigits, monetaryFormat);
|
||||
}
|
||||
|
||||
public String formatCoin(Coin coin, int decimalPlaces, boolean decimalAligned, int maxNumberOfDigits, MonetaryFormat coinFormat) {
|
||||
public static String formatCoin(Coin coin,
|
||||
int decimalPlaces,
|
||||
boolean decimalAligned,
|
||||
int maxNumberOfDigits,
|
||||
MonetaryFormat coinFormat) {
|
||||
String formattedCoin = "";
|
||||
|
||||
if (coin != null) {
|
||||
@ -132,18 +131,18 @@ public class BSFormatter {
|
||||
}
|
||||
|
||||
public String formatCoinWithCode(Coin coin) {
|
||||
return formatCoinWithCode(coin, coinFormat);
|
||||
return formatCoinWithCode(coin, monetaryFormat);
|
||||
}
|
||||
|
||||
public String formatCoinWithCode(long value) {
|
||||
return formatCoinWithCode(Coin.valueOf(value), monetaryFormat);
|
||||
}
|
||||
|
||||
public static String formatCoinWithCode(long value, MonetaryFormat coinFormat) {
|
||||
return formatCoinWithCode(Coin.valueOf(value), coinFormat);
|
||||
}
|
||||
|
||||
public String formatCoinWithCode(long value, MonetaryFormat coinFormat) {
|
||||
return formatCoinWithCode(Coin.valueOf(value), coinFormat);
|
||||
}
|
||||
|
||||
public String formatCoinWithCode(Coin coin, MonetaryFormat coinFormat) {
|
||||
public static String formatCoinWithCode(Coin coin, MonetaryFormat coinFormat) {
|
||||
if (coin != null) {
|
||||
try {
|
||||
// we don't use the code feature from coinFormat as it does automatic switching between mBTC and BTC and
|
||||
@ -158,62 +157,11 @@ public class BSFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
public Coin parseToCoin(String input) {
|
||||
return parseToCoin(input, coinFormat);
|
||||
}
|
||||
|
||||
public Coin parseToCoin(String input, MonetaryFormat coinFormat) {
|
||||
if (input != null && input.length() > 0) {
|
||||
try {
|
||||
return coinFormat.parse(cleanDoubleInput(input));
|
||||
} catch (Throwable t) {
|
||||
log.warn("Exception at parseToBtc: " + t.toString());
|
||||
return Coin.ZERO;
|
||||
}
|
||||
} else {
|
||||
return Coin.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts to a coin with max. 4 decimal places. Last place gets rounded.
|
||||
* 0.01234 -> 0.0123
|
||||
* 0.01235 -> 0.0124
|
||||
*
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
public Coin parseToCoinWith4Decimals(String input) {
|
||||
try {
|
||||
return Coin.valueOf(new BigDecimal(parseToCoin(cleanDoubleInput(input)).value).setScale(-scale - 1,
|
||||
BigDecimal.ROUND_HALF_UP).setScale(scale + 1, BigDecimal.ROUND_HALF_UP).toBigInteger().longValue());
|
||||
} catch (Throwable t) {
|
||||
if (input != null && input.length() > 0)
|
||||
log.warn("Exception at parseToCoinWith4Decimals: " + t.toString());
|
||||
return Coin.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasBtcValidDecimals(String input) {
|
||||
return parseToCoin(input).equals(parseToCoinWith4Decimals(input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a coin with the properties defined in the format (used to reduce decimal places)
|
||||
*
|
||||
* @param coin The coin which should be transformed
|
||||
* @return The transformed coin
|
||||
*/
|
||||
public Coin reduceTo4Decimals(Coin coin) {
|
||||
return parseToCoin(formatCoin(coin));
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// FIAT
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public String formatFiat(Fiat fiat, MonetaryFormat format, boolean appendCurrencyCode) {
|
||||
public static String formatFiat(Fiat fiat, MonetaryFormat format, boolean appendCurrencyCode) {
|
||||
if (fiat != null) {
|
||||
try {
|
||||
final String res = format.noCode().format(fiat).toString();
|
||||
@ -230,10 +178,10 @@ public class BSFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
protected Fiat parseToFiat(String input, String currencyCode) {
|
||||
private static Fiat parseToFiat(String input, String currencyCode) {
|
||||
if (input != null && input.length() > 0) {
|
||||
try {
|
||||
return Fiat.parseFiat(currencyCode, cleanDoubleInput(input));
|
||||
return Fiat.parseFiat(currencyCode, ParsingUtils.cleanDoubleInput(input));
|
||||
} catch (Exception e) {
|
||||
log.warn("Exception at parseToFiat: " + e.toString());
|
||||
return Fiat.valueOf(currencyCode, 0);
|
||||
@ -253,10 +201,10 @@ public class BSFormatter {
|
||||
* @return
|
||||
*/
|
||||
|
||||
public Fiat parseToFiatWithPrecision(String input, String currencyCode) {
|
||||
public static Fiat parseToFiatWithPrecision(String input, String currencyCode) {
|
||||
if (input != null && input.length() > 0) {
|
||||
try {
|
||||
return parseToFiat(new BigDecimal(cleanDoubleInput(input)).setScale(2, BigDecimal.ROUND_HALF_UP).toString(),
|
||||
return parseToFiat(new BigDecimal(ParsingUtils.cleanDoubleInput(input)).setScale(2, BigDecimal.ROUND_HALF_UP).toString(),
|
||||
currencyCode);
|
||||
} catch (Throwable t) {
|
||||
log.warn("Exception at parseToFiatWithPrecision: " + t.toString());
|
||||
@ -267,24 +215,12 @@ public class BSFormatter {
|
||||
return Fiat.valueOf(currencyCode, 0);
|
||||
}
|
||||
|
||||
public boolean isFiatAlteredWhenPrecisionApplied(String input, String currencyCode) {
|
||||
return parseToFiat(input, currencyCode).equals(parseToFiatWithPrecision(input, currencyCode));
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Altcoin
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public String formatAltcoin(Altcoin altcoin) {
|
||||
return formatAltcoin(altcoin, false);
|
||||
}
|
||||
|
||||
public String formatAltcoinWithCode(Altcoin altcoin) {
|
||||
return formatAltcoin(altcoin, true);
|
||||
}
|
||||
|
||||
public String formatAltcoin(Altcoin altcoin, boolean appendCurrencyCode) {
|
||||
private static String formatAltcoin(Altcoin altcoin, boolean appendCurrencyCode) {
|
||||
if (altcoin != null) {
|
||||
try {
|
||||
String res = altcoinFormat.noCode().format(altcoin).toString();
|
||||
@ -306,21 +242,8 @@ public class BSFormatter {
|
||||
// Volume
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public String formatVolume(Offer offer, Boolean decimalAligned, int maxNumberOfDigits) {
|
||||
return formatVolume(offer, decimalAligned, maxNumberOfDigits, true);
|
||||
}
|
||||
|
||||
public String formatVolume(Offer offer, Boolean decimalAligned, int maxNumberOfDigits, boolean showRange) {
|
||||
String formattedVolume = offer.isRange() && showRange ? formatVolume(offer.getMinVolume()) + RANGE_SEPARATOR + formatVolume(offer.getVolume()) : formatVolume(offer.getVolume());
|
||||
|
||||
if (decimalAligned) {
|
||||
formattedVolume = fillUpPlacesWithEmptyStrings(formattedVolume, maxNumberOfDigits);
|
||||
}
|
||||
return formattedVolume;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String fillUpPlacesWithEmptyStrings(String formattedNumber, int maxNumberOfDigits) {
|
||||
public static String fillUpPlacesWithEmptyStrings(String formattedNumber, int maxNumberOfDigits) {
|
||||
//FIXME: temporary deactivate adding spaces in front of numbers as we don't use a monospace font right now.
|
||||
/*int numberOfPlacesToFill = maxNumberOfDigits - formattedNumber.length();
|
||||
for (int i = 0; i < numberOfPlacesToFill; i++) {
|
||||
@ -329,27 +252,7 @@ public class BSFormatter {
|
||||
return formattedNumber;
|
||||
}
|
||||
|
||||
public String formatVolume(Volume volume) {
|
||||
return formatVolume(volume, fiatVolumeFormat, false);
|
||||
}
|
||||
|
||||
public String formatVolumeWithCode(Volume volume) {
|
||||
return formatVolume(volume, fiatVolumeFormat, true);
|
||||
}
|
||||
|
||||
public String formatVolume(Volume volume, MonetaryFormat fiatVolumeFormat, boolean appendCurrencyCode) {
|
||||
if (volume != null) {
|
||||
Monetary monetary = volume.getMonetary();
|
||||
if (monetary instanceof Fiat)
|
||||
return formatFiat((Fiat) monetary, fiatVolumeFormat, appendCurrencyCode);
|
||||
else
|
||||
return formatAltcoinVolume((Altcoin) monetary, appendCurrencyCode);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public String formatAltcoinVolume(Altcoin altcoin, boolean appendCurrencyCode) {
|
||||
public static String formatAltcoinVolume(Altcoin altcoin, boolean appendCurrencyCode) {
|
||||
if (altcoin != null) {
|
||||
try {
|
||||
// TODO quick hack...
|
||||
@ -371,47 +274,13 @@ public class BSFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
public String formatVolumeLabel(String currencyCode) {
|
||||
return formatVolumeLabel(currencyCode, "");
|
||||
}
|
||||
|
||||
public String formatVolumeLabel(String currencyCode, String postFix) {
|
||||
return Res.get("formatter.formatVolumeLabel",
|
||||
currencyCode, postFix);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Amount
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public String formatAmount(Offer offer) {
|
||||
return formatAmount(offer, false);
|
||||
}
|
||||
|
||||
public String formatAmount(Offer offer, boolean decimalAligned) {
|
||||
String formattedAmount = offer.isRange() ? formatCoin(offer.getMinAmount()) + RANGE_SEPARATOR + formatCoin(offer.getAmount()) : formatCoin(offer.getAmount());
|
||||
if (decimalAligned) {
|
||||
formattedAmount = fillUpPlacesWithEmptyStrings(formattedAmount, 15);
|
||||
}
|
||||
return formattedAmount;
|
||||
}
|
||||
|
||||
public String formatAmount(Offer offer, int decimalPlaces, boolean decimalAligned, int maxPlaces) {
|
||||
String formattedAmount = offer.isRange() ? formatCoin(offer.getMinAmount(), decimalPlaces) + RANGE_SEPARATOR + formatCoin(offer.getAmount(), decimalPlaces) : formatCoin(offer.getAmount(), decimalPlaces);
|
||||
|
||||
if (decimalAligned) {
|
||||
formattedAmount = fillUpPlacesWithEmptyStrings(formattedAmount, maxPlaces);
|
||||
}
|
||||
return formattedAmount;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Price
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
public String formatPrice(Price price, MonetaryFormat fiatPriceFormat, boolean appendCurrencyCode) {
|
||||
public static String formatPrice(Price price, MonetaryFormat fiatPriceFormat, boolean appendCurrencyCode) {
|
||||
if (price != null) {
|
||||
Monetary monetary = price.getMonetary();
|
||||
if (monetary instanceof Fiat)
|
||||
@ -423,35 +292,26 @@ public class BSFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
public String formatPrice(Price price, boolean appendCurrencyCode) {
|
||||
public static String formatPrice(Price price, boolean appendCurrencyCode) {
|
||||
return formatPrice(price, fiatPriceFormat, true);
|
||||
}
|
||||
|
||||
public String formatPrice(Price price) {
|
||||
public static String formatPrice(Price price) {
|
||||
return formatPrice(price, fiatPriceFormat, false);
|
||||
}
|
||||
|
||||
public String formatPrice(Price price, Boolean decimalAligned, int maxPlaces) {
|
||||
String formattedPrice = formatPrice(price);
|
||||
|
||||
if (decimalAligned) {
|
||||
formattedPrice = fillUpPlacesWithEmptyStrings(formattedPrice, maxPlaces);
|
||||
}
|
||||
return formattedPrice;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Market price
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public String formatMarketPrice(double price, String currencyCode) {
|
||||
public static String formatMarketPrice(double price, String currencyCode) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode))
|
||||
return formatMarketPrice(price, 2);
|
||||
else
|
||||
return formatMarketPrice(price, 8);
|
||||
}
|
||||
|
||||
public String formatMarketPrice(double price, int precision) {
|
||||
public static String formatMarketPrice(double price, int precision) {
|
||||
return formatRoundedDoubleWithPrecision(price, precision);
|
||||
}
|
||||
|
||||
@ -460,40 +320,25 @@ public class BSFormatter {
|
||||
// Other
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public String formatRoundedDoubleWithPrecision(double value, int precision) {
|
||||
public static String formatRoundedDoubleWithPrecision(double value, int precision) {
|
||||
decimalFormat.setMinimumFractionDigits(precision);
|
||||
decimalFormat.setMaximumFractionDigits(precision);
|
||||
return decimalFormat.format(MathUtils.roundDouble(value, precision)).replace(",", ".");
|
||||
}
|
||||
|
||||
public String getDirectionWithCode(OfferPayload.Direction direction, String currencyCode) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode))
|
||||
return (direction == OfferPayload.Direction.BUY) ? Res.get("shared.buyCurrency", Res.getBaseCurrencyCode()) : Res.get("shared.sellCurrency", Res.getBaseCurrencyCode());
|
||||
else
|
||||
return (direction == OfferPayload.Direction.SELL) ? Res.get("shared.buyCurrency", currencyCode) : Res.get("shared.sellCurrency", currencyCode);
|
||||
}
|
||||
|
||||
public String getDirectionWithCodeDetailed(OfferPayload.Direction direction, String currencyCode) {
|
||||
public static String getDirectionWithCodeDetailed(OfferPayload.Direction direction, String currencyCode) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode))
|
||||
return (direction == OfferPayload.Direction.BUY) ? Res.get("shared.buyingBTCWith", currencyCode) : Res.get("shared.sellingBTCFor", currencyCode);
|
||||
else
|
||||
return (direction == OfferPayload.Direction.SELL) ? Res.get("shared.buyingCurrency", currencyCode) : Res.get("shared.sellingCurrency", currencyCode);
|
||||
}
|
||||
|
||||
public String arbitratorAddressesToString(List<NodeAddress> nodeAddresses) {
|
||||
public static String arbitratorAddressesToString(List<NodeAddress> nodeAddresses) {
|
||||
return nodeAddresses.stream().map(NodeAddress::getFullAddress).collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
public String languageCodesToString(List<String> languageLocales) {
|
||||
return languageLocales.stream().map(LanguageUtil::getDisplayName).collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
public String formatDateTime(Date date) {
|
||||
return formatDateTime(date, true);
|
||||
}
|
||||
|
||||
public String formatDateTime(Date date, boolean useLocaleAndLocalTimezone) {
|
||||
Locale locale = useLocaleAndLocalTimezone ? getLocale() : Locale.US;
|
||||
public static String formatDateTime(Date date, boolean useLocaleAndLocalTimezone) {
|
||||
Locale locale = useLocaleAndLocalTimezone ? GlobalSettings.getLocale() : Locale.US;
|
||||
DateFormat dateInstance = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
|
||||
DateFormat timeInstance = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
|
||||
if (!useLocaleAndLocalTimezone) {
|
||||
@ -503,7 +348,7 @@ public class BSFormatter {
|
||||
return formatDateTime(date, dateInstance, timeInstance);
|
||||
}
|
||||
|
||||
public String formatDateTime(Date date, DateFormat dateFormatter, DateFormat timeFormatter) {
|
||||
public static String formatDateTime(Date date, DateFormat dateFormatter, DateFormat timeFormatter) {
|
||||
if (date != null) {
|
||||
return dateFormatter.format(date) + " " + timeFormatter.format(date);
|
||||
} else {
|
||||
@ -511,113 +356,26 @@ public class BSFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
public String formatDateTimeSpan(Date dateFrom, Date dateTo) {
|
||||
if (dateFrom != null && dateTo != null) {
|
||||
DateFormat dateFormatter = DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale());
|
||||
DateFormat timeFormatter = DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale());
|
||||
return dateFormatter.format(dateFrom) + " " + timeFormatter.format(dateFrom) + RANGE_SEPARATOR + timeFormatter.format(dateTo);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public String formatTime(Date date) {
|
||||
if (date != null) {
|
||||
DateFormat timeFormatter = DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale());
|
||||
return timeFormatter.format(date);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public String formatDate(Date date) {
|
||||
if (date != null) {
|
||||
DateFormat dateFormatter = DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale());
|
||||
return dateFormatter.format(date);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public String formatToPercentWithSymbol(double value) {
|
||||
public static String formatToPercentWithSymbol(double value) {
|
||||
return formatToPercent(value) + "%";
|
||||
}
|
||||
|
||||
public String formatPercentagePrice(double value) {
|
||||
public static String formatPercentagePrice(double value) {
|
||||
return formatToPercentWithSymbol(value);
|
||||
}
|
||||
|
||||
public String formatToPercent(double value) {
|
||||
public static String formatToPercent(double value) {
|
||||
DecimalFormat decimalFormat = new DecimalFormat("#.##");
|
||||
decimalFormat.setMinimumFractionDigits(2);
|
||||
decimalFormat.setMaximumFractionDigits(2);
|
||||
return decimalFormat.format(MathUtils.roundDouble(value * 100.0, 2)).replace(",", ".");
|
||||
}
|
||||
|
||||
public double parseNumberStringToDouble(String input) throws NumberFormatException {
|
||||
return Double.parseDouble(cleanDoubleInput(input));
|
||||
}
|
||||
|
||||
public double parsePercentStringToDouble(String percentString) throws NumberFormatException {
|
||||
String input = percentString.replace("%", "");
|
||||
input = cleanDoubleInput(input);
|
||||
double value = Double.parseDouble(input);
|
||||
return MathUtils.roundDouble(value / 100d, 4);
|
||||
}
|
||||
|
||||
public long parsePriceStringToLong(String currencyCode, String amount, int precision) {
|
||||
if (amount == null || amount.isEmpty())
|
||||
return 0;
|
||||
|
||||
long value = 0;
|
||||
try {
|
||||
double amountValue = Double.parseDouble(amount);
|
||||
amount = formatRoundedDoubleWithPrecision(amountValue, precision);
|
||||
value = Price.parse(currencyCode, amount).getValue();
|
||||
} catch (NumberFormatException ignore) {
|
||||
// expected NumberFormatException if input is not a number
|
||||
} catch (Throwable t) {
|
||||
log.error("parsePriceStringToLong: " + t.toString());
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static String convertCharsForNumber(String input) {
|
||||
// Some languages like finnish use the long dash for the minus
|
||||
input = input.replace("−", "-");
|
||||
input = StringUtils.deleteWhitespace(input);
|
||||
return input.replace(",", ".");
|
||||
}
|
||||
|
||||
protected String cleanDoubleInput(String input) {
|
||||
input = convertCharsForNumber(input);
|
||||
if (input.equals("."))
|
||||
input = input.replace(".", "0.");
|
||||
if (input.equals("-."))
|
||||
input = input.replace("-.", "-0.");
|
||||
// don't use String.valueOf(Double.parseDouble(input)) as return value as it gives scientific
|
||||
// notation (1.0E-6) which screw up coinFormat.parse
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
// Just called to check if we have a valid double, throws exception otherwise
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
Double.parseDouble(input);
|
||||
return input;
|
||||
}
|
||||
|
||||
public String formatAccountAge(long durationMillis) {
|
||||
durationMillis = Math.max(0, durationMillis);
|
||||
String day = Res.get("time.day").toLowerCase();
|
||||
String days = Res.get("time.days");
|
||||
String format = "d\' " + days + "\'";
|
||||
return StringUtils.replaceOnce(DurationFormatUtils.formatDuration(durationMillis, format), "1 " + days, "1 " + day);
|
||||
}
|
||||
|
||||
public String formatDurationAsWords(long durationMillis) {
|
||||
public static String formatDurationAsWords(long durationMillis) {
|
||||
return formatDurationAsWords(durationMillis, false, true);
|
||||
}
|
||||
|
||||
public String formatDurationAsWords(long durationMillis, boolean showSeconds, boolean showZeroValues) {
|
||||
public static String formatDurationAsWords(long durationMillis, boolean showSeconds, boolean showZeroValues) {
|
||||
String format = "";
|
||||
String second = Res.get("time.second");
|
||||
String minute = Res.get("time.minute");
|
||||
@ -657,77 +415,7 @@ public class BSFormatter {
|
||||
return duration.trim();
|
||||
}
|
||||
|
||||
public String booleanToYesNo(boolean value) {
|
||||
return value ? Res.get("shared.yes") : Res.get("shared.no");
|
||||
}
|
||||
|
||||
public String getDirectionBothSides(OfferPayload.Direction direction, String currencyCode) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
currencyCode = Res.getBaseCurrencyCode();
|
||||
return direction == OfferPayload.Direction.BUY ?
|
||||
Res.get("formatter.makerTaker", currencyCode, Res.get("shared.buyer"), currencyCode, Res.get("shared.seller")) :
|
||||
Res.get("formatter.makerTaker", currencyCode, Res.get("shared.seller"), currencyCode, Res.get("shared.buyer"));
|
||||
} else {
|
||||
return direction == OfferPayload.Direction.SELL ?
|
||||
Res.get("formatter.makerTaker", currencyCode, Res.get("shared.buyer"), currencyCode, Res.get("shared.seller")) :
|
||||
Res.get("formatter.makerTaker", currencyCode, Res.get("shared.seller"), currencyCode, Res.get("shared.buyer"));
|
||||
}
|
||||
}
|
||||
|
||||
public String getDirectionForBuyer(boolean isMyOffer, String currencyCode) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
String code = Res.getBaseCurrencyCode();
|
||||
return isMyOffer ?
|
||||
Res.get("formatter.youAreAsMaker", Res.get("shared.buying"), code, Res.get("shared.selling"), code) :
|
||||
Res.get("formatter.youAreAsTaker", Res.get("shared.buying"), code, Res.get("shared.selling"), code);
|
||||
} else {
|
||||
return isMyOffer ?
|
||||
Res.get("formatter.youAreAsMaker", Res.get("shared.selling"), currencyCode, Res.get("shared.buying"), currencyCode) :
|
||||
Res.get("formatter.youAreAsTaker", Res.get("shared.selling"), currencyCode, Res.get("shared.buying"), currencyCode);
|
||||
}
|
||||
}
|
||||
|
||||
public String getDirectionForSeller(boolean isMyOffer, String currencyCode) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
String code = Res.getBaseCurrencyCode();
|
||||
return isMyOffer ?
|
||||
Res.get("formatter.youAreAsMaker", Res.get("shared.selling"), code, Res.get("shared.buying"), code) :
|
||||
Res.get("formatter.youAreAsTaker", Res.get("shared.selling"), code, Res.get("shared.buying"), code);
|
||||
} else {
|
||||
return isMyOffer ?
|
||||
Res.get("formatter.youAreAsMaker", Res.get("shared.buying"), currencyCode, Res.get("shared.selling"), currencyCode) :
|
||||
Res.get("formatter.youAreAsTaker", Res.get("shared.buying"), currencyCode, Res.get("shared.selling"), currencyCode);
|
||||
}
|
||||
}
|
||||
|
||||
public String getDirectionForTakeOffer(OfferPayload.Direction direction, String currencyCode) {
|
||||
String baseCurrencyCode = Res.getBaseCurrencyCode();
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
return direction == OfferPayload.Direction.BUY ?
|
||||
Res.get("formatter.youAre", Res.get("shared.selling"), baseCurrencyCode, Res.get("shared.buying"), currencyCode) :
|
||||
Res.get("formatter.youAre", Res.get("shared.buying"), baseCurrencyCode, Res.get("shared.selling"), currencyCode);
|
||||
} else {
|
||||
|
||||
return direction == OfferPayload.Direction.SELL ?
|
||||
Res.get("formatter.youAre", Res.get("shared.selling"), currencyCode, Res.get("shared.buying"), baseCurrencyCode) :
|
||||
Res.get("formatter.youAre", Res.get("shared.buying"), currencyCode, Res.get("shared.selling"), baseCurrencyCode);
|
||||
}
|
||||
}
|
||||
|
||||
public String getOfferDirectionForCreateOffer(OfferPayload.Direction direction, String currencyCode) {
|
||||
String baseCurrencyCode = Res.getBaseCurrencyCode();
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
return direction == OfferPayload.Direction.BUY ?
|
||||
Res.get("formatter.youAreCreatingAnOffer.fiat", Res.get("shared.buy"), baseCurrencyCode) :
|
||||
Res.get("formatter.youAreCreatingAnOffer.fiat", Res.get("shared.sell"), baseCurrencyCode);
|
||||
} else {
|
||||
return direction == OfferPayload.Direction.SELL ?
|
||||
Res.get("formatter.youAreCreatingAnOffer.altcoin", Res.get("shared.buy"), currencyCode, Res.get("shared.selling"), baseCurrencyCode) :
|
||||
Res.get("formatter.youAreCreatingAnOffer.altcoin", Res.get("shared.sell"), currencyCode, Res.get("shared.buying"), baseCurrencyCode);
|
||||
}
|
||||
}
|
||||
|
||||
public String getRole(boolean isBuyerMakerAndSellerTaker, boolean isMaker, String currencyCode) {
|
||||
public static String getRole(boolean isBuyerMakerAndSellerTaker, boolean isMaker, String currencyCode) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
String baseCurrencyCode = Res.getBaseCurrencyCode();
|
||||
if (isBuyerMakerAndSellerTaker)
|
||||
@ -751,7 +439,7 @@ public class BSFormatter {
|
||||
|
||||
}
|
||||
|
||||
public String formatBytes(long bytes) {
|
||||
public static String formatBytes(long bytes) {
|
||||
double kb = 1024;
|
||||
double mb = kb * kb;
|
||||
DecimalFormat decimalFormat = new DecimalFormat("#.##");
|
||||
@ -763,47 +451,28 @@ public class BSFormatter {
|
||||
return decimalFormat.format(bytes / mb) + " MB";
|
||||
}
|
||||
|
||||
public String getCurrencyPair(String currencyCode) {
|
||||
public static String getCurrencyPair(String currencyCode) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode))
|
||||
return Res.getBaseCurrencyCode() + "/" + currencyCode;
|
||||
else
|
||||
return currencyCode + "/" + Res.getBaseCurrencyCode();
|
||||
}
|
||||
|
||||
public String getCounterCurrency(String currencyCode) {
|
||||
public static String getCounterCurrency(String currencyCode) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode))
|
||||
return currencyCode;
|
||||
else
|
||||
return Res.getBaseCurrencyCode();
|
||||
}
|
||||
|
||||
public String getBaseCurrency(String currencyCode) {
|
||||
if (CurrencyUtil.isCryptoCurrency(currencyCode))
|
||||
return currencyCode;
|
||||
else
|
||||
return Res.getBaseCurrencyCode();
|
||||
}
|
||||
|
||||
public String getCounterCurrencyAndCurrencyPair(String currencyCode) {
|
||||
return getCounterCurrency(currencyCode) + " (" + getCurrencyPair(currencyCode) + ")";
|
||||
}
|
||||
|
||||
public String getCurrencyNameAndCurrencyPair(String currencyCode) {
|
||||
return CurrencyUtil.getNameByCode(currencyCode) + " (" + getCurrencyPair(currencyCode) + ")";
|
||||
}
|
||||
|
||||
public String getPriceWithCurrencyCode(String currencyCode) {
|
||||
public static String getPriceWithCurrencyCode(String currencyCode) {
|
||||
return getPriceWithCurrencyCode(currencyCode, "shared.priceInCurForCur");
|
||||
}
|
||||
|
||||
public String getPriceWithCurrencyCode(String currencyCode, String translationKey) {
|
||||
public static String getPriceWithCurrencyCode(String currencyCode, String translationKey) {
|
||||
if (CurrencyUtil.isCryptoCurrency(currencyCode))
|
||||
return Res.get(translationKey, Res.getBaseCurrencyCode(), currencyCode);
|
||||
else
|
||||
return Res.get(translationKey, currencyCode, Res.getBaseCurrencyCode());
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return GlobalSettings.getLocale();
|
||||
}
|
||||
}
|
||||
|
@ -61,12 +61,12 @@ public class BsqFormatter extends BSFormatter {
|
||||
GlobalSettings.localeProperty().addListener((observable, oldValue, newValue) -> setFormatter(newValue));
|
||||
setFormatter(GlobalSettings.getLocale());
|
||||
|
||||
btcCoinFormat = super.coinFormat;
|
||||
btcCoinFormat = super.monetaryFormat;
|
||||
|
||||
final String baseCurrencyCode = BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode();
|
||||
switch (baseCurrencyCode) {
|
||||
case "BTC":
|
||||
coinFormat = new MonetaryFormat().shift(6).code(6, "BSQ").minDecimals(2);
|
||||
monetaryFormat = new MonetaryFormat().shift(6).code(6, "BSQ").minDecimals(2);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("baseCurrencyCode not defined. baseCurrencyCode=" + baseCurrencyCode);
|
||||
@ -123,11 +123,11 @@ public class BsqFormatter extends BSFormatter {
|
||||
}
|
||||
|
||||
public String formatBSQSatoshis(long satoshi) {
|
||||
return super.formatCoin(satoshi, coinFormat);
|
||||
return super.formatCoin(satoshi, monetaryFormat);
|
||||
}
|
||||
|
||||
public String formatBSQSatoshisWithCode(long satoshi) {
|
||||
return super.formatCoinWithCode(satoshi, coinFormat);
|
||||
return super.formatCoinWithCode(satoshi, monetaryFormat);
|
||||
}
|
||||
|
||||
public String formatBTCSatoshis(long satoshi) {
|
||||
@ -135,11 +135,11 @@ public class BsqFormatter extends BSFormatter {
|
||||
}
|
||||
|
||||
public String formatBTCWithCode(long satoshi) {
|
||||
return super.formatCoinWithCode(satoshi, btcCoinFormat);
|
||||
return BSFormatter.formatCoinWithCode(satoshi, btcCoinFormat);
|
||||
}
|
||||
|
||||
public String formatBTCWithCode(Coin coin) {
|
||||
return super.formatCoinWithCode(coin, btcCoinFormat);
|
||||
return BSFormatter.formatCoinWithCode(coin, btcCoinFormat);
|
||||
}
|
||||
|
||||
public String formatBTC(Coin coin) {
|
||||
@ -147,7 +147,7 @@ public class BsqFormatter extends BSFormatter {
|
||||
}
|
||||
|
||||
public Coin parseToBTC(String input) {
|
||||
return super.parseToCoin(input, btcCoinFormat);
|
||||
return ParsingUtils.parseToCoin(input, btcCoinFormat);
|
||||
}
|
||||
|
||||
public void validateBtcInput(String input) throws ProposalValidationException {
|
||||
@ -155,12 +155,12 @@ public class BsqFormatter extends BSFormatter {
|
||||
}
|
||||
|
||||
public void validateBsqInput(String input) throws ProposalValidationException {
|
||||
validateCoinInput(input, this.coinFormat);
|
||||
validateCoinInput(input, this.monetaryFormat);
|
||||
}
|
||||
|
||||
private void validateCoinInput(String input, MonetaryFormat coinFormat) throws ProposalValidationException {
|
||||
try {
|
||||
coinFormat.parse(cleanDoubleInput(input));
|
||||
coinFormat.parse(ParsingUtils.cleanDoubleInput(input));
|
||||
} catch (Throwable t) {
|
||||
throw new ProposalValidationException("Invalid format for a " + coinFormat.code() + " value");
|
||||
}
|
||||
@ -172,11 +172,11 @@ public class BsqFormatter extends BSFormatter {
|
||||
// In case we add a new param old clients will not know that enum and fall back to UNDEFINED.
|
||||
return Res.get("shared.na");
|
||||
case BSQ:
|
||||
return formatCoinWithCode(parseToCoin(value));
|
||||
return formatCoinWithCode(ParsingUtils.parseToCoin(value, this));
|
||||
case BTC:
|
||||
return formatBTCWithCode(parseToBTC(value));
|
||||
case PERCENT:
|
||||
return formatToPercentWithSymbol(parsePercentStringToDouble(value));
|
||||
return formatToPercentWithSymbol(ParsingUtils.parsePercentStringToDouble(value));
|
||||
case BLOCK:
|
||||
return Res.get("dao.param.blocks", Integer.parseInt(value));
|
||||
case ADDRESS:
|
||||
@ -190,7 +190,7 @@ public class BsqFormatter extends BSFormatter {
|
||||
public Coin parseParamValueToCoin(Param param, String inputValue) {
|
||||
switch (param.getParamType()) {
|
||||
case BSQ:
|
||||
return parseToCoin(inputValue);
|
||||
return ParsingUtils.parseToCoin(inputValue, this);
|
||||
case BTC:
|
||||
return parseToBTC(inputValue);
|
||||
default:
|
||||
@ -216,7 +216,7 @@ public class BsqFormatter extends BSFormatter {
|
||||
case BTC:
|
||||
return formatBTC(parseParamValueToCoin(param, inputValue));
|
||||
case PERCENT:
|
||||
return formatToPercent(parsePercentStringToDouble(inputValue));
|
||||
return formatToPercent(ParsingUtils.parsePercentStringToDouble(inputValue));
|
||||
case BLOCK:
|
||||
return Integer.toString(parseParamValueToBlocks(param, inputValue));
|
||||
case ADDRESS:
|
||||
|
83
core/src/main/java/bisq/core/util/ParsingUtils.java
Normal file
83
core/src/main/java/bisq/core/util/ParsingUtils.java
Normal file
@ -0,0 +1,83 @@
|
||||
package bisq.core.util;
|
||||
|
||||
import bisq.core.monetary.Price;
|
||||
|
||||
import bisq.common.util.MathUtils;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.utils.MonetaryFormat;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ParsingUtils {
|
||||
public static Coin parseToCoin(String input, BSFormatter bsFormatter) {
|
||||
return parseToCoin(input, bsFormatter.getMonetaryFormat());
|
||||
}
|
||||
|
||||
public static Coin parseToCoin(String input, MonetaryFormat coinFormat) {
|
||||
if (input != null && input.length() > 0) {
|
||||
try {
|
||||
return coinFormat.parse(cleanDoubleInput(input));
|
||||
} catch (Throwable t) {
|
||||
log.warn("Exception at parseToBtc: " + t.toString());
|
||||
return Coin.ZERO;
|
||||
}
|
||||
} else {
|
||||
return Coin.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
public static double parseNumberStringToDouble(String input) throws NumberFormatException {
|
||||
return Double.parseDouble(cleanDoubleInput(input));
|
||||
}
|
||||
|
||||
public static double parsePercentStringToDouble(String percentString) throws NumberFormatException {
|
||||
String input = percentString.replace("%", "");
|
||||
input = cleanDoubleInput(input);
|
||||
double value = Double.parseDouble(input);
|
||||
return MathUtils.roundDouble(value / 100d, 4);
|
||||
}
|
||||
|
||||
public static long parsePriceStringToLong(String currencyCode, String amount, int precision) {
|
||||
if (amount == null || amount.isEmpty())
|
||||
return 0;
|
||||
|
||||
long value = 0;
|
||||
try {
|
||||
double amountValue = Double.parseDouble(amount);
|
||||
amount = BSFormatter.formatRoundedDoubleWithPrecision(amountValue, precision);
|
||||
value = Price.parse(currencyCode, amount).getValue();
|
||||
} catch (NumberFormatException ignore) {
|
||||
// expected NumberFormatException if input is not a number
|
||||
} catch (Throwable t) {
|
||||
log.error("parsePriceStringToLong: " + t.toString());
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static String convertCharsForNumber(String input) {
|
||||
// Some languages like finnish use the long dash for the minus
|
||||
input = input.replace("−", "-");
|
||||
input = StringUtils.deleteWhitespace(input);
|
||||
return input.replace(",", ".");
|
||||
}
|
||||
|
||||
public static String cleanDoubleInput(String input) {
|
||||
input = convertCharsForNumber(input);
|
||||
if (input.equals("."))
|
||||
input = input.replace(".", "0.");
|
||||
if (input.equals("-."))
|
||||
input = input.replace("-.", "-0.");
|
||||
// don't use String.valueOf(Double.parseDouble(input)) as return value as it gives scientific
|
||||
// notation (1.0E-6) which screw up coinFormat.parse
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
// Just called to check if we have a valid double, throws exception otherwise
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
Double.parseDouble(input);
|
||||
return input;
|
||||
}
|
||||
}
|
@ -506,7 +506,7 @@ takeOffer.failed.offerTaken=You cannot take that offer because the offer was alr
|
||||
takeOffer.failed.offerRemoved=You cannot take that offer because the offer has been removed in the meantime.
|
||||
takeOffer.failed.offererNotOnline=Take offer request failed because maker is not online anymore.
|
||||
takeOffer.failed.offererOffline=You cannot take that offer because the maker is offline.
|
||||
takeOffer.warning.connectionToPeerLost=You lost connection to the maker.\nHe might have gone offline or has closed the connection to you because of too many open connections.\n\nIf you can still see his offer in the offerbook you can try to take the offer again.
|
||||
takeOffer.warning.connectionToPeerLost=You lost connection to the maker.\nThey might have gone offline or has closed the connection to you because of too many open connections.\n\nIf you can still see their offer in the offerbook you can try to take the offer again.
|
||||
|
||||
takeOffer.error.noFundsLost=\n\nNo funds have left your wallet yet.\nPlease try to restart your application and check your network connection to see if you can resolve the issue.
|
||||
takeOffer.error.feePaid=\n\nPlease try to restart your application and check your network connection to see if you can resolve the issue.
|
||||
@ -613,7 +613,7 @@ portfolio.pending.step2_buyer.confirmStart.yes=Yes, I have started the payment
|
||||
portfolio.pending.step2_seller.waitPayment.headline=Wait for payment
|
||||
portfolio.pending.step2_seller.f2fInfo.headline=Buyer's contact information
|
||||
portfolio.pending.step2_seller.waitPayment.msg=The deposit transaction has at least one blockchain confirmation.\nYou need to wait until the BTC buyer starts the {0} payment.
|
||||
portfolio.pending.step2_seller.warn=The BTC buyer still has not done the {0} payment.\nYou need to wait until they have started the payment.\nThe trade has to be completed by {1}.
|
||||
portfolio.pending.step2_seller.warn=The BTC buyer still has not done the {0} payment.\nYou need to wait until they have started the payment.\nIf the trade has not been completed on {1} the arbitrator will investigate.
|
||||
portfolio.pending.step2_seller.openForDispute=The BTC buyer has not started their payment!\nThe max. allowed period for the trade has elapsed.\nYou can wait longer and give the trading peer more time or contact the mediator for assistance.
|
||||
|
||||
tradeChat.chatWindowTitle=Chat window for trade with ID ''{0}''
|
||||
@ -779,7 +779,7 @@ portfolio.pending.mediationRequested=Mediation requested
|
||||
portfolio.pending.openSupport=Open support ticket
|
||||
portfolio.pending.supportTicketOpened=Support ticket opened
|
||||
portfolio.pending.requestSupport=Request support
|
||||
portfolio.pending.error.requestSupport=Please report the problem to your mediator or arbitrator.\n\nHe will forward the \
|
||||
portfolio.pending.error.requestSupport=Please report the problem to your mediator or arbitrator.\n\nThey will forward the \
|
||||
information to the developers to investigate the problem.\nAfter the problem has been analyzed you will \
|
||||
get back all locked funds.
|
||||
portfolio.pending.communicateWithArbitrator=Please communicate in the \"Support\" screen with the arbitrator.
|
||||
@ -910,7 +910,7 @@ support.filter=Filter list
|
||||
support.filter.prompt=Enter trade ID, date, onion address or account data
|
||||
support.noTickets=There are no open tickets
|
||||
support.sendingMessage=Sending Message...
|
||||
support.receiverNotOnline=Receiver is not online. Message is saved to his mailbox.
|
||||
support.receiverNotOnline=Receiver is not online. Message is saved to their mailbox.
|
||||
support.sendMessageError=Sending message failed. Error: {0}
|
||||
support.wrongVersion=The offer in that dispute has been created with an older version of Bisq.\n\
|
||||
You cannot close that dispute with your version of the application.\n\n\
|
||||
@ -1874,7 +1874,7 @@ dao.proposal.create.publish=Publish proposal
|
||||
dao.proposal.create.publishing=Proposal publishing is in progress ...
|
||||
dao.proposal=proposal
|
||||
dao.proposal.display.type=Proposal type
|
||||
dao.proposal.display.name=Name/nickname
|
||||
dao.proposal.display.name=Exact GitHub username
|
||||
dao.proposal.display.link=Link to detailed info
|
||||
dao.proposal.display.link.prompt=Link to proposal
|
||||
dao.proposal.display.requestedBsq=Requested amount in BSQ
|
||||
|
2501
core/src/main/resources/i18n/displayStrings_pt_BR.properties
Normal file
2501
core/src/main/resources/i18n/displayStrings_pt_BR.properties
Normal file
File diff suppressed because it is too large
Load Diff
@ -46,7 +46,7 @@ public class ArbitratorTest {
|
||||
return new Arbitrator(new NodeAddress("host", 1000),
|
||||
getBytes(100),
|
||||
"btcaddress",
|
||||
new PubKeyRing(getBytes(100), getBytes(100), "key"),
|
||||
new PubKeyRing(getBytes(100), getBytes(100)),
|
||||
Lists.newArrayList(),
|
||||
new Date().getTime(),
|
||||
getBytes(100),
|
||||
@ -58,4 +58,3 @@ public class ArbitratorTest {
|
||||
return RandomUtils.nextBytes(count);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ public class MediatorTest {
|
||||
|
||||
public static Mediator getMediatorMock() {
|
||||
return new Mediator(new NodeAddress("host", 1000),
|
||||
new PubKeyRing(getBytes(100), getBytes(100), "key"),
|
||||
new PubKeyRing(getBytes(100), getBytes(100)),
|
||||
Lists.newArrayList(),
|
||||
new Date().getTime(),
|
||||
getBytes(100),
|
||||
@ -53,6 +53,4 @@ public class MediatorTest {
|
||||
"info",
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -22,11 +22,8 @@ import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.KeyStorage;
|
||||
import bisq.common.storage.FileUtil;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
import java.io.File;
|
||||
@ -62,5 +59,3 @@ public class EncryptionTest {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd $(dirname $0)
|
||||
tx pull -l de,el_GR,es,ja,pt,ru,zh_CN,vi,th_TH,fa,fr
|
||||
tx pull -l de,el_GR,es,ja,pt,ru,zh_CN,vi,th_TH,fa,fr,pt_BR
|
||||
|
||||
translations="translations/bisq-desktop.displaystringsproperties"
|
||||
i18n="src/main/resources/i18n"
|
||||
@ -17,5 +17,6 @@ mv "$translations/vi.properties" "$i18n/displayStrings_vi.properties"
|
||||
mv "$translations/th_TH.properties" "$i18n/displayStrings_th.properties"
|
||||
mv "$translations/fa.properties" "$i18n/displayStrings_fa.properties"
|
||||
mv "$translations/fr.properties" "$i18n/displayStrings_fr.properties"
|
||||
mv "$translations/pt_BR.properties" "$i18n/displayStrings_pt_BR.properties"
|
||||
|
||||
rm -rf $translations
|
||||
|
@ -26,7 +26,7 @@
|
||||
<string>public.app-category.finance</string>
|
||||
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2013-2018 - The Bisq developers</string>
|
||||
<string>Copyright © 2013-2019 - The Bisq developers</string>
|
||||
|
||||
<!-- Only supported in older OSX versions.
|
||||
See: https://github.com/bitcoin/bitcoin/issues/11896#issuecomment-352148399-->
|
||||
|
43
desktop/package/macosx/create_desktop_for_testing.sh
Executable file
43
desktop/package/macosx/create_desktop_for_testing.sh
Executable file
@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd $(dirname $0)/../../
|
||||
|
||||
mkdir -p deploy
|
||||
|
||||
set -e
|
||||
|
||||
version="1.1.5-SNAPSHOT"
|
||||
commithash="ec633f0c3771893b47956b8d05b17c6f3f1919c1"
|
||||
|
||||
cd ..
|
||||
./gradlew :desktop:build -x test shadowJar
|
||||
cd desktop
|
||||
|
||||
EXE_JAR=build/libs/desktop-$version-all.jar
|
||||
JAR_WITH_HASH_NAME=desktop-$version-$commithash-all.jar
|
||||
EXE_JAR_WITH_HASH=build/libs/$JAR_WITH_HASH_NAME
|
||||
DEPLOY_JAR=deploy/$JAR_WITH_HASH_NAME
|
||||
|
||||
# we need to strip out Java 9 module configuration used in the fontawesomefx library as it causes the javapackager to stop,
|
||||
# because of this existing module information, although it is not used as a module.
|
||||
echo Unzipping jar to delete module config
|
||||
tmp=build/libs/tmp
|
||||
unzip -o -q $EXE_JAR -d $tmp
|
||||
|
||||
# Sometimes $tmp/module-info.class is not available. TODO check why and if still needed
|
||||
rm -f $tmp/module-info.class
|
||||
|
||||
rm $EXE_JAR
|
||||
echo Zipping jar again without module config
|
||||
cd $tmp; zip -r -q -X "../$JAR_WITH_HASH_NAME" *
|
||||
cd ../../../; rm -rf $tmp
|
||||
|
||||
cp $EXE_JAR_WITH_HASH $DEPLOY_JAR
|
||||
|
||||
echo Create signature
|
||||
gpg --digest-algo SHA256 --local-user $BISQ_GPG_USER --output $DEPLOY_JAR.asc --detach-sig --armor $DEPLOY_JAR
|
||||
|
||||
echo Verify signatures
|
||||
gpg --digest-algo SHA256 --verify $DEPLOY_JAR{.asc*,}
|
||||
|
||||
open deploy
|
43
desktop/package/macosx/create_seednode_for_testing.sh
Executable file
43
desktop/package/macosx/create_seednode_for_testing.sh
Executable file
@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd $(dirname $0)/../../
|
||||
|
||||
set -e
|
||||
|
||||
version="1.1.5-SNAPSHOT"
|
||||
commithash="ec633f0c3771893b47956b8d05b17c6f3f1919c1"
|
||||
|
||||
cd ..
|
||||
./gradlew :seednode:build -x test shadowJar
|
||||
cd seednode
|
||||
|
||||
mkdir -p deploy
|
||||
|
||||
EXE_JAR=build/libs/seednode-all.jar
|
||||
JAR_WITH_HASH_NAME=seednode-$version-$commithash-all.jar
|
||||
EXE_JAR_WITH_HASH=build/libs/$JAR_WITH_HASH_NAME
|
||||
DEPLOY_JAR=deploy/$JAR_WITH_HASH_NAME
|
||||
|
||||
# we need to strip out Java 9 module configuration used in the fontawesomefx library as it causes the javapackager to stop,
|
||||
# because of this existing module information, although it is not used as a module.
|
||||
echo Unzipping jar to delete module config
|
||||
tmp=build/libs/tmp
|
||||
unzip -o -q $EXE_JAR -d $tmp
|
||||
|
||||
# Sometimes $tmp/module-info.class is not available. TODO check why and if still needed
|
||||
rm -f $tmp/module-info.class
|
||||
|
||||
rm $EXE_JAR
|
||||
echo Zipping jar again without module config
|
||||
cd $tmp; zip -r -q -X "../$JAR_WITH_HASH_NAME" *
|
||||
cd ../../../; rm -rf $tmp
|
||||
|
||||
cp $EXE_JAR_WITH_HASH $DEPLOY_JAR
|
||||
|
||||
echo Create signature
|
||||
gpg --digest-algo SHA256 --local-user $BISQ_GPG_USER --output $DEPLOY_JAR.asc --detach-sig --armor $DEPLOY_JAR
|
||||
|
||||
echo Verify signatures
|
||||
gpg --digest-algo SHA256 --verify $DEPLOY_JAR{.asc*,}
|
||||
|
||||
open deploy
|
@ -20,7 +20,6 @@ package bisq.desktop.app;
|
||||
import bisq.desktop.common.view.CachingViewLoader;
|
||||
import bisq.desktop.common.view.View;
|
||||
import bisq.desktop.common.view.ViewLoader;
|
||||
import bisq.desktop.components.AutoTooltipLabel;
|
||||
import bisq.desktop.main.MainView;
|
||||
import bisq.desktop.main.debug.DebugView;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
@ -29,13 +28,14 @@ import bisq.desktop.main.overlays.windows.FilterWindow;
|
||||
import bisq.desktop.main.overlays.windows.ManualPayoutTxWindow;
|
||||
import bisq.desktop.main.overlays.windows.SendAlertMessageWindow;
|
||||
import bisq.desktop.main.overlays.windows.ShowWalletDataWindow;
|
||||
import bisq.desktop.util.ImageUtil;
|
||||
import bisq.desktop.util.CssTheme;
|
||||
import bisq.desktop.util.ImageUtil;
|
||||
|
||||
import bisq.core.alert.AlertManager;
|
||||
import bisq.core.app.AppOptionKeys;
|
||||
import bisq.core.app.AvoidStandbyModeService;
|
||||
import bisq.core.app.BisqEnvironment;
|
||||
import bisq.core.app.OSXStandbyModeDisabler;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.WalletsManager;
|
||||
import bisq.core.dao.governance.voteresult.MissingDataRequestService;
|
||||
@ -57,8 +57,6 @@ import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.name.Names;
|
||||
|
||||
import org.reactfx.EventStreams;
|
||||
|
||||
import javafx.application.Application;
|
||||
|
||||
import javafx.stage.Modality;
|
||||
@ -67,11 +65,9 @@ import javafx.stage.StageStyle;
|
||||
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import java.awt.GraphicsEnvironment;
|
||||
@ -139,6 +135,7 @@ public class BisqApp extends Application implements UncaughtExceptionHandler {
|
||||
scene = createAndConfigScene(mainView, injector);
|
||||
setupStage(scene);
|
||||
|
||||
injector.getInstance(OSXStandbyModeDisabler.class).doIt();
|
||||
injector.getInstance(AvoidStandbyModeService.class).init();
|
||||
|
||||
UserThread.runPeriodically(() -> Profiler.printSystemLoad(log), LOG_MEMORY_PERIOD_MIN, TimeUnit.MINUTES);
|
||||
@ -318,14 +315,10 @@ public class BisqApp extends Application implements UncaughtExceptionHandler {
|
||||
else
|
||||
new Popup<>().warning(Res.get("popup.warning.walletNotInitialized")).show();
|
||||
} else if (DevEnv.isDevMode()) {
|
||||
// dev ode only
|
||||
if (Utilities.isAltOrCtrlPressed(KeyCode.P, keyEvent)) {
|
||||
showFPSWindow(scene);
|
||||
} else if (Utilities.isAltOrCtrlPressed(KeyCode.Z, keyEvent)) {
|
||||
if (Utilities.isAltOrCtrlPressed(KeyCode.Z, keyEvent))
|
||||
showDebugWindow(scene, injector);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -401,30 +394,4 @@ public class BisqApp extends Application implements UncaughtExceptionHandler {
|
||||
stage.setY(this.stage.getY());
|
||||
stage.show();
|
||||
}
|
||||
|
||||
private void showFPSWindow(Scene scene) {
|
||||
Label label = new AutoTooltipLabel();
|
||||
EventStreams.animationTicks()
|
||||
.latestN(100)
|
||||
.map(ticks -> {
|
||||
int n = ticks.size() - 1;
|
||||
return n * 1_000_000_000.0 / (ticks.get(n) - ticks.get(0));
|
||||
})
|
||||
.map(d -> String.format("FPS: %.3f", d)) // Don't translate, just for dev
|
||||
.feedTo(label.textProperty());
|
||||
|
||||
Pane root = new StackPane();
|
||||
root.getChildren().add(label);
|
||||
Stage stage = new Stage();
|
||||
stage.setScene(new Scene(root));
|
||||
stage.setTitle("FPS"); // Don't translate, just for dev
|
||||
stage.initModality(Modality.NONE);
|
||||
stage.initStyle(StageStyle.UTILITY);
|
||||
stage.initOwner(scene.getWindow());
|
||||
stage.setX(this.stage.getX() + this.stage.getWidth() + 10);
|
||||
stage.setY(this.stage.getY());
|
||||
stage.setWidth(200);
|
||||
stage.setHeight(100);
|
||||
stage.show();
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,9 @@
|
||||
}
|
||||
.root {
|
||||
-fx-font-size: 13;
|
||||
}
|
||||
|
||||
.root:dir(ltr){
|
||||
-fx-font-family: "IBM Plex Sans";
|
||||
}
|
||||
|
||||
|
@ -18,8 +18,7 @@
|
||||
package bisq.desktop.common;
|
||||
|
||||
import bisq.common.Timer;
|
||||
|
||||
import org.reactfx.util.FxTimer;
|
||||
import bisq.common.reactfx.FxTimer;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@ -28,7 +27,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
public class UITimer implements Timer {
|
||||
private final Logger log = LoggerFactory.getLogger(UITimer.class);
|
||||
private org.reactfx.util.Timer timer;
|
||||
private bisq.common.reactfx.Timer timer;
|
||||
|
||||
public UITimer() {
|
||||
}
|
||||
|
@ -18,12 +18,11 @@
|
||||
package bisq.desktop.components;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.desktop.components.controlsfx.control.PopOver;
|
||||
|
||||
import de.jensd.fx.fontawesome.AwesomeDude;
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
|
||||
import org.controlsfx.control.PopOver;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
@ -18,12 +18,11 @@
|
||||
package bisq.desktop.components;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.desktop.components.controlsfx.control.PopOver;
|
||||
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
import de.jensd.fx.glyphs.GlyphIcons;
|
||||
|
||||
import org.controlsfx.control.PopOver;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
|
@ -18,11 +18,10 @@
|
||||
package bisq.desktop.components;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.desktop.components.controlsfx.control.PopOver;
|
||||
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
|
||||
import org.controlsfx.control.PopOver;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
|
@ -18,13 +18,12 @@
|
||||
package bisq.desktop.components;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.desktop.components.controlsfx.control.PopOver;
|
||||
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
|
||||
import org.controlsfx.control.PopOver;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
|
@ -40,8 +40,7 @@ import javafx.beans.property.SimpleObjectProperty;
|
||||
* There can be only 1 errorMessageDisplays at a time we use static field for it.
|
||||
* The position is derived from the position of the textField itself or if set from the layoutReference node.
|
||||
*/
|
||||
//TODO There are some rare situation where it behaves buggy. Needs further investigation and improvements. Also
|
||||
// consider replacement with controlsFX components.
|
||||
//TODO There are some rare situation where it behaves buggy. Needs further investigation and improvements.
|
||||
public class InputTextField extends JFXTextField {
|
||||
|
||||
private final ObjectProperty<InputValidator.ValidationResult> validationResult = new SimpleObjectProperty<>
|
||||
|
@ -18,6 +18,7 @@
|
||||
package bisq.desktop.components;
|
||||
|
||||
import bisq.desktop.main.overlays.editor.PeerInfoWithTagEditor;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.alert.PrivateNotificationManager;
|
||||
@ -141,7 +142,7 @@ public class PeerInfoIcon extends Group {
|
||||
boolean isFiatCurrency = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode());
|
||||
|
||||
String accountAge = isFiatCurrency ?
|
||||
peersAccountAge > -1 ? Res.get("peerInfoIcon.tooltip.age", formatter.formatAccountAge(peersAccountAge)) :
|
||||
peersAccountAge > -1 ? Res.get("peerInfoIcon.tooltip.age", DisplayUtils.formatAccountAge(peersAccountAge)) :
|
||||
Res.get("peerInfoIcon.tooltip.unknownAge") :
|
||||
"";
|
||||
tooltipText = hasTraded ?
|
||||
@ -268,7 +269,7 @@ public class PeerInfoIcon extends Group {
|
||||
long makersAccountAge) {
|
||||
final String accountAgeTagEditor = isFiatCurrency ?
|
||||
makersAccountAge > -1 ?
|
||||
formatter.formatAccountAge(makersAccountAge) :
|
||||
DisplayUtils.formatAccountAge(makersAccountAge) :
|
||||
Res.get("peerInfo.unknownAge") :
|
||||
null;
|
||||
setOnMouseClicked(e -> new PeerInfoWithTagEditor(privateNotificationManager, offer, preferences, useDevPrivilegeKeys)
|
||||
|
@ -0,0 +1,13 @@
|
||||
This package is a very minimal subset of the external library `controlsfx`.
|
||||
|
||||
Three files were embedded into the project to avoid having `controlsfx` as dependency.
|
||||
|
||||
This is based on version `8.0.6_20` tagged in commit 6a52afec3ef16094cda281abc80b4daa3d3bf1fd:
|
||||
|
||||
[https://github.com/controlsfx/controlsfx/commit/6a52afec3ef16094cda281abc80b4daa3d3bf1fd]
|
||||
|
||||
These specific files got raw copied (with package name adjustment):
|
||||
|
||||
[https://github.com/controlsfx/controlsfx/blob/6a52afec3ef16094cda281abc80b4daa3d3bf1fd/controlsfx/src/main/java/org/controlsfx/control/PopOver.java]
|
||||
[https://github.com/controlsfx/controlsfx/blob/6a52afec3ef16094cda281abc80b4daa3d3bf1fd/controlsfx/src/main/java/impl/org/controlsfx/skin/PopOverSkin.java]
|
||||
[https://github.com/controlsfx/controlsfx/blob/6a52afec3ef16094cda281abc80b4daa3d3bf1fd/controlsfx/src/main/resources/org/controlsfx/control/popover.css]
|
@ -0,0 +1,787 @@
|
||||
/**
|
||||
* Copyright (c) 2013, ControlsFX
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of ControlsFX, any associated website, nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package bisq.desktop.components.controlsfx.control;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static javafx.scene.input.MouseEvent.MOUSE_CLICKED;
|
||||
import bisq.desktop.components.controlsfx.skin.PopOverSkin;
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.PopupControl;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
import javafx.util.Duration;
|
||||
|
||||
/**
|
||||
* The PopOver control provides detailed information about an owning node in a
|
||||
* popup window. The popup window has a very lightweight appearance (no default
|
||||
* window decorations) and an arrow pointing at the owner. Due to the nature of
|
||||
* popup windows the PopOver will move around with the parent window when the
|
||||
* user drags it. <br>
|
||||
* <center> <img src="popover.png"/> </center> <br>
|
||||
* The PopOver can be detached from the owning node by dragging it away from the
|
||||
* owner. It stops displaying an arrow and starts displaying a title and a close
|
||||
* icon. <br>
|
||||
* <br>
|
||||
* <center> <img src="popover-detached.png"/> </center> <br>
|
||||
* The following image shows a popover with an accordion content node. PopOver
|
||||
* controls are automatically resizing themselves when the content node changes
|
||||
* its size.<br>
|
||||
* <br>
|
||||
* <center> <img src="popover-accordion.png"/> </center> <br>
|
||||
*/
|
||||
public class PopOver extends PopupControl {
|
||||
|
||||
private static final String DEFAULT_STYLE_CLASS = "popover"; //$NON-NLS-1$
|
||||
|
||||
private static final Duration DEFAULT_FADE_DURATION = Duration.seconds(.2);
|
||||
|
||||
private double targetX;
|
||||
|
||||
private double targetY;
|
||||
|
||||
/**
|
||||
* Creates a pop over with a label as the content node.
|
||||
*/
|
||||
public PopOver() {
|
||||
super();
|
||||
|
||||
getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
|
||||
setAnchorLocation(AnchorLocation.WINDOW_TOP_LEFT);
|
||||
setOnHiding(new EventHandler<WindowEvent>() {
|
||||
@Override
|
||||
public void handle(WindowEvent evt) {
|
||||
setDetached(false);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Create some initial content.
|
||||
*/
|
||||
Label label = new Label("<No Content>"); //$NON-NLS-1$
|
||||
label.setPrefSize(200, 200);
|
||||
label.setPadding(new Insets(4));
|
||||
setContentNode(label);
|
||||
|
||||
ChangeListener<Object> repositionListener = new ChangeListener<Object>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Object> value,
|
||||
Object oldObject, Object newObject) {
|
||||
if (isShowing() && !isDetached()) {
|
||||
show(getOwnerNode(), targetX, targetY);
|
||||
adjustWindowLocation();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
arrowSize.addListener(repositionListener);
|
||||
cornerRadius.addListener(repositionListener);
|
||||
arrowLocation.addListener(repositionListener);
|
||||
arrowIndent.addListener(repositionListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pop over with the given node as the content node.
|
||||
*
|
||||
* @param content The content shown by the pop over
|
||||
*/
|
||||
public PopOver(Node content) {
|
||||
this();
|
||||
|
||||
setContentNode(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new PopOverSkin(this);
|
||||
}
|
||||
|
||||
// Content support.
|
||||
|
||||
private final ObjectProperty<Node> contentNode = new SimpleObjectProperty<Node>(
|
||||
this, "contentNode") { //$NON-NLS-1$
|
||||
@Override
|
||||
public void setValue(Node node) {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"content node can not be null"); //$NON-NLS-1$
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the content shown by the pop over.
|
||||
*
|
||||
* @return the content node property
|
||||
*/
|
||||
public final ObjectProperty<Node> contentNodeProperty() {
|
||||
return contentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the content property
|
||||
*
|
||||
* @return the content node
|
||||
*
|
||||
* @see #contentNodeProperty()
|
||||
*/
|
||||
public final Node getContentNode() {
|
||||
return contentNodeProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the content property.
|
||||
*
|
||||
* @param content
|
||||
* the new content node value
|
||||
*
|
||||
* @see #contentNodeProperty()
|
||||
*/
|
||||
public final void setContentNode(Node content) {
|
||||
contentNodeProperty().set(content);
|
||||
}
|
||||
|
||||
private InvalidationListener hideListener = new InvalidationListener() {
|
||||
@Override
|
||||
public void invalidated(Observable observable) {
|
||||
if (!isDetached()) {
|
||||
hide(Duration.ZERO);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private WeakInvalidationListener weakHideListener = new WeakInvalidationListener(
|
||||
hideListener);
|
||||
|
||||
private ChangeListener<Number> xListener = new ChangeListener<Number>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Number> value,
|
||||
Number oldX, Number newX) {
|
||||
setX(getX() + (newX.doubleValue() - oldX.doubleValue()));
|
||||
}
|
||||
};
|
||||
|
||||
private WeakChangeListener<Number> weakXListener = new WeakChangeListener<>(
|
||||
xListener);
|
||||
|
||||
private ChangeListener<Number> yListener = new ChangeListener<Number>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Number> value,
|
||||
Number oldY, Number newY) {
|
||||
setY(getY() + (newY.doubleValue() - oldY.doubleValue()));
|
||||
}
|
||||
};
|
||||
|
||||
private WeakChangeListener<Number> weakYListener = new WeakChangeListener<>(
|
||||
yListener);
|
||||
|
||||
private Window ownerWindow;
|
||||
|
||||
/**
|
||||
* Shows the pop over in a position relative to the edges of the given owner
|
||||
* node. The position is dependent on the arrow location. If the arrow is
|
||||
* pointing to the right then the pop over will be placed to the left of the
|
||||
* given owner. If the arrow points up then the pop over will be placed
|
||||
* below the given owner node. The arrow will slightly overlap with the
|
||||
* owner node.
|
||||
*
|
||||
* @param owner
|
||||
* the owner of the pop over
|
||||
*/
|
||||
public final void show(Node owner) {
|
||||
show(owner, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the pop over in a position relative to the edges of the given owner
|
||||
* node. The position is dependent on the arrow location. If the arrow is
|
||||
* pointing to the right then the pop over will be placed to the left of the
|
||||
* given owner. If the arrow points up then the pop over will be placed
|
||||
* below the given owner node.
|
||||
*
|
||||
* @param owner
|
||||
* the owner of the pop over
|
||||
* @param offset
|
||||
* if negative specifies the distance to the owner node or when
|
||||
* positive specifies the number of pixels that the arrow will
|
||||
* overlap with the owner node (positive values are recommended)
|
||||
*/
|
||||
public final void show(Node owner, double offset) {
|
||||
requireNonNull(owner);
|
||||
|
||||
Bounds bounds = owner.localToScreen(owner.getBoundsInLocal());
|
||||
|
||||
switch (getArrowLocation()) {
|
||||
case BOTTOM_CENTER:
|
||||
case BOTTOM_LEFT:
|
||||
case BOTTOM_RIGHT:
|
||||
show(owner, bounds.getMinX() + bounds.getWidth() / 2,
|
||||
bounds.getMinY() + offset);
|
||||
break;
|
||||
case LEFT_BOTTOM:
|
||||
case LEFT_CENTER:
|
||||
case LEFT_TOP:
|
||||
show(owner, bounds.getMaxX() - offset,
|
||||
bounds.getMinY() + bounds.getHeight() / 2);
|
||||
break;
|
||||
case RIGHT_BOTTOM:
|
||||
case RIGHT_CENTER:
|
||||
case RIGHT_TOP:
|
||||
show(owner, bounds.getMinX() + offset,
|
||||
bounds.getMinY() + bounds.getHeight() / 2);
|
||||
break;
|
||||
case TOP_CENTER:
|
||||
case TOP_LEFT:
|
||||
case TOP_RIGHT:
|
||||
show(owner, bounds.getMinX() + bounds.getWidth() / 2,
|
||||
bounds.getMinY() + bounds.getHeight() - offset);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the pop over visible at the give location and associates it with
|
||||
* the given owner node. The x and y coordinate will be the target location
|
||||
* of the arrow of the pop over and not the location of the window.
|
||||
*
|
||||
* @param owner
|
||||
* the owning node
|
||||
* @param x
|
||||
* the x coordinate for the pop over arrow tip
|
||||
* @param y
|
||||
* the y coordinate for the pop over arrow tip
|
||||
*/
|
||||
@Override
|
||||
public final void show(Node owner, double x, double y) {
|
||||
show(owner, x, y, DEFAULT_FADE_DURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the pop over visible at the give location and associates it with
|
||||
* the given owner node. The x and y coordinate will be the target location
|
||||
* of the arrow of the pop over and not the location of the window.
|
||||
*
|
||||
* @param owner
|
||||
* the owning node
|
||||
* @param x
|
||||
* the x coordinate for the pop over arrow tip
|
||||
* @param y
|
||||
* the y coordinate for the pop over arrow tip
|
||||
* @param fadeInDuration
|
||||
* the time it takes for the pop over to be fully visible
|
||||
*/
|
||||
public final void show(Node owner, double x, double y,
|
||||
Duration fadeInDuration) {
|
||||
|
||||
/*
|
||||
* Calling show() a second time without first closing the
|
||||
* pop over causes it to be placed at the wrong location.
|
||||
*/
|
||||
if (ownerWindow != null && isShowing()) {
|
||||
super.hide();
|
||||
}
|
||||
|
||||
targetX = x;
|
||||
targetY = y;
|
||||
|
||||
if (owner == null) {
|
||||
throw new IllegalArgumentException("owner can not be null"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
if (fadeInDuration == null) {
|
||||
fadeInDuration = DEFAULT_FADE_DURATION;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is all needed because children windows do not get their x and y
|
||||
* coordinate updated when the owning window gets moved by the user.
|
||||
*/
|
||||
if (ownerWindow != null) {
|
||||
ownerWindow.xProperty().removeListener(weakXListener);
|
||||
ownerWindow.yProperty().removeListener(weakYListener);
|
||||
ownerWindow.widthProperty().removeListener(weakHideListener);
|
||||
ownerWindow.heightProperty().removeListener(weakHideListener);
|
||||
}
|
||||
|
||||
ownerWindow = owner.getScene().getWindow();
|
||||
ownerWindow.xProperty().addListener(weakXListener);
|
||||
ownerWindow.yProperty().addListener(weakYListener);
|
||||
ownerWindow.widthProperty().addListener(weakHideListener);
|
||||
ownerWindow.heightProperty().addListener(weakHideListener);
|
||||
|
||||
setOnShown(evt -> {
|
||||
|
||||
/*
|
||||
* The user clicked somewhere into the transparent background. If
|
||||
* this is the case the hide the window (when attached).
|
||||
*/
|
||||
getScene().addEventHandler(MOUSE_CLICKED,
|
||||
new EventHandler<MouseEvent>() {
|
||||
public void handle(MouseEvent evt) {
|
||||
if (evt.getTarget().equals(getScene().getRoot())) {
|
||||
if (!isDetached()) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/*
|
||||
* Move the window so that the arrow will end up pointing at the
|
||||
* target coordinates.
|
||||
*/
|
||||
adjustWindowLocation();
|
||||
});
|
||||
|
||||
super.show(owner, x, y);
|
||||
|
||||
// Fade In
|
||||
Node skinNode = getSkin().getNode();
|
||||
skinNode.setOpacity(0);
|
||||
|
||||
FadeTransition fadeIn = new FadeTransition(fadeInDuration, skinNode);
|
||||
fadeIn.setFromValue(0);
|
||||
fadeIn.setToValue(1);
|
||||
fadeIn.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the pop over by quickly changing its opacity to 0.
|
||||
*
|
||||
* @see #hide(Duration)
|
||||
*/
|
||||
@Override
|
||||
public final void hide() {
|
||||
hide(DEFAULT_FADE_DURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the pop over by quickly changing its opacity to 0.
|
||||
*
|
||||
* @param fadeOutDuration
|
||||
* the duration of the fade transition that is being used to
|
||||
* change the opacity of the pop over
|
||||
* @since 1.0
|
||||
*/
|
||||
public final void hide(Duration fadeOutDuration) {
|
||||
if (fadeOutDuration == null) {
|
||||
fadeOutDuration = DEFAULT_FADE_DURATION;
|
||||
}
|
||||
|
||||
if (isShowing()) {
|
||||
// Fade Out
|
||||
Node skinNode = getSkin().getNode();
|
||||
skinNode.setOpacity(0);
|
||||
|
||||
FadeTransition fadeOut = new FadeTransition(fadeOutDuration,
|
||||
skinNode);
|
||||
fadeOut.setFromValue(1);
|
||||
fadeOut.setToValue(0);
|
||||
fadeOut.setOnFinished(evt -> super.hide());
|
||||
fadeOut.play();
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustWindowLocation() {
|
||||
Bounds bounds = PopOver.this.getSkin().getNode().getBoundsInParent();
|
||||
|
||||
switch (getArrowLocation()) {
|
||||
case TOP_CENTER:
|
||||
case TOP_LEFT:
|
||||
case TOP_RIGHT:
|
||||
setX(getX() + bounds.getMinX() - computeXOffset());
|
||||
setY(getY() + bounds.getMinY() + getArrowSize());
|
||||
break;
|
||||
case LEFT_TOP:
|
||||
case LEFT_CENTER:
|
||||
case LEFT_BOTTOM:
|
||||
setX(getX() + bounds.getMinX() + getArrowSize());
|
||||
setY(getY() + bounds.getMinY() - computeYOffset());
|
||||
break;
|
||||
case BOTTOM_CENTER:
|
||||
case BOTTOM_LEFT:
|
||||
case BOTTOM_RIGHT:
|
||||
setX(getX() + bounds.getMinX() - computeXOffset());
|
||||
setY(getY() - bounds.getMinY() - bounds.getMaxY() - 1);
|
||||
break;
|
||||
case RIGHT_TOP:
|
||||
case RIGHT_BOTTOM:
|
||||
case RIGHT_CENTER:
|
||||
setX(getX() - bounds.getMinX() - bounds.getMaxX() - 1);
|
||||
setY(getY() + bounds.getMinY() - computeYOffset());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private double computeXOffset() {
|
||||
switch (getArrowLocation()) {
|
||||
case TOP_LEFT:
|
||||
case BOTTOM_LEFT:
|
||||
return getCornerRadius() + getArrowIndent() + getArrowSize();
|
||||
case TOP_CENTER:
|
||||
case BOTTOM_CENTER:
|
||||
return getContentNode().prefWidth(-1) / 2;
|
||||
case TOP_RIGHT:
|
||||
case BOTTOM_RIGHT:
|
||||
return getContentNode().prefWidth(-1) - getArrowIndent()
|
||||
- getCornerRadius() - getArrowSize();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private double computeYOffset() {
|
||||
double prefContentHeight = getContentNode().prefHeight(-1);
|
||||
|
||||
switch (getArrowLocation()) {
|
||||
case LEFT_TOP:
|
||||
case RIGHT_TOP:
|
||||
return getCornerRadius() + getArrowIndent() + getArrowSize();
|
||||
case LEFT_CENTER:
|
||||
case RIGHT_CENTER:
|
||||
return Math.max(prefContentHeight, 2 * (getCornerRadius()
|
||||
+ getArrowIndent() + getArrowSize())) / 2;
|
||||
case LEFT_BOTTOM:
|
||||
case RIGHT_BOTTOM:
|
||||
return Math.max(prefContentHeight - getCornerRadius()
|
||||
- getArrowIndent() - getArrowSize(), getCornerRadius()
|
||||
+ getArrowIndent() + getArrowSize());
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the pop over from the owning node. The pop over will no longer
|
||||
* display an arrow pointing at the owner node.
|
||||
*/
|
||||
public final void detach() {
|
||||
if (isDetachable()) {
|
||||
setDetached(true);
|
||||
}
|
||||
}
|
||||
|
||||
// detach support
|
||||
|
||||
private final BooleanProperty detachable = new SimpleBooleanProperty(this,
|
||||
"detachable", true); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Determines if the pop over is detachable at all.
|
||||
*/
|
||||
public final BooleanProperty detachableProperty() {
|
||||
return detachable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the detachable property.
|
||||
*
|
||||
* @param detachable
|
||||
* if true then the user can detach / tear off the pop over
|
||||
*
|
||||
* @see #detachableProperty()
|
||||
*/
|
||||
public final void setDetachable(boolean detachable) {
|
||||
detachableProperty().set(detachable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the detachable property.
|
||||
*
|
||||
* @return true if the user is allowed to detach / tear off the pop over
|
||||
*
|
||||
* @see #detachableProperty()
|
||||
*/
|
||||
public final boolean isDetachable() {
|
||||
return detachableProperty().get();
|
||||
}
|
||||
|
||||
private final BooleanProperty detached = new SimpleBooleanProperty(this,
|
||||
"detached", false); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Determines whether the pop over is detached from the owning node or not.
|
||||
* A detached pop over no longer shows an arrow pointing at the owner and
|
||||
* features its own title bar.
|
||||
*
|
||||
* @return the detached property
|
||||
*/
|
||||
public final BooleanProperty detachedProperty() {
|
||||
return detached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the detached property.
|
||||
*
|
||||
* @param detached
|
||||
* if true the pop over will change its apperance to "detached"
|
||||
* mode
|
||||
*
|
||||
* @see #detachedProperty()
|
||||
*/
|
||||
public final void setDetached(boolean detached) {
|
||||
detachedProperty().set(detached);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the detached property.
|
||||
*
|
||||
* @return true if the pop over is currently detached.
|
||||
*
|
||||
* @see #detachedProperty()
|
||||
*/
|
||||
public final boolean isDetached() {
|
||||
return detachedProperty().get();
|
||||
}
|
||||
|
||||
// arrow size support
|
||||
|
||||
// TODO: make styleable
|
||||
|
||||
private final DoubleProperty arrowSize = new SimpleDoubleProperty(this,
|
||||
"arrowSize", 12); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Controls the size of the arrow. Default value is 12.
|
||||
*
|
||||
* @return the arrow size property
|
||||
*/
|
||||
public final DoubleProperty arrowSizeProperty() {
|
||||
return arrowSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the arrow size property.
|
||||
*
|
||||
* @return the arrow size property value
|
||||
*
|
||||
* @see #arrowSizeProperty()
|
||||
*/
|
||||
public final double getArrowSize() {
|
||||
return arrowSizeProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the arrow size property.
|
||||
*
|
||||
* @param size
|
||||
* the new value of the arrow size property
|
||||
*
|
||||
* @see #arrowSizeProperty()
|
||||
*/
|
||||
public final void setArrowSize(double size) {
|
||||
arrowSizeProperty().set(size);
|
||||
}
|
||||
|
||||
// arrow indent support
|
||||
|
||||
// TODO: make styleable
|
||||
|
||||
private final DoubleProperty arrowIndent = new SimpleDoubleProperty(this,
|
||||
"arrowIndent", 12); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Controls the distance between the arrow and the corners of the pop over.
|
||||
* The default value is 12.
|
||||
*
|
||||
* @return the arrow indent property
|
||||
*/
|
||||
public final DoubleProperty arrowIndentProperty() {
|
||||
return arrowIndent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the arrow indent property.
|
||||
*
|
||||
* @return the arrow indent value
|
||||
*
|
||||
* @see #arrowIndentProperty()
|
||||
*/
|
||||
public final double getArrowIndent() {
|
||||
return arrowIndentProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the arrow indent property.
|
||||
*
|
||||
* @param size
|
||||
* the arrow indent value
|
||||
*
|
||||
* @see #arrowIndentProperty()
|
||||
*/
|
||||
public final void setArrowIndent(double size) {
|
||||
arrowIndentProperty().set(size);
|
||||
}
|
||||
|
||||
// radius support
|
||||
|
||||
// TODO: make styleable
|
||||
|
||||
private final DoubleProperty cornerRadius = new SimpleDoubleProperty(this,
|
||||
"cornerRadius", 6); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Returns the corner radius property for the pop over.
|
||||
*
|
||||
* @return the corner radius property (default is 6)
|
||||
*/
|
||||
public final DoubleProperty cornerRadiusProperty() {
|
||||
return cornerRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the corner radius property.
|
||||
*
|
||||
* @return the corner radius
|
||||
*
|
||||
* @see #cornerRadiusProperty()
|
||||
*/
|
||||
public final double getCornerRadius() {
|
||||
return cornerRadiusProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the corner radius property.
|
||||
*
|
||||
* @param radius
|
||||
* the corner radius
|
||||
*
|
||||
* @see #cornerRadiusProperty()
|
||||
*/
|
||||
public final void setCornerRadius(double radius) {
|
||||
cornerRadiusProperty().set(radius);
|
||||
}
|
||||
|
||||
// Detached stage title
|
||||
|
||||
private final StringProperty detachedTitle = new SimpleStringProperty(this,
|
||||
"detachedTitle", "Info"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
/**
|
||||
* Stores the title to display when the pop over becomes detached.
|
||||
*
|
||||
* @return the detached title property
|
||||
*/
|
||||
public final StringProperty detachedTitleProperty() {
|
||||
return detachedTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the detached title property.
|
||||
*
|
||||
* @return the detached title
|
||||
*
|
||||
* @see #detachedTitleProperty()
|
||||
*/
|
||||
public final String getDetachedTitle() {
|
||||
return detachedTitleProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the detached title property.
|
||||
*
|
||||
* @param title
|
||||
* the title to use when detached
|
||||
*
|
||||
* @see #detachedTitleProperty()
|
||||
*/
|
||||
public final void setDetachedTitle(String title) {
|
||||
if (title == null) {
|
||||
throw new IllegalArgumentException("title can not be null"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
detachedTitleProperty().set(title);
|
||||
}
|
||||
|
||||
private final ObjectProperty<ArrowLocation> arrowLocation = new SimpleObjectProperty<PopOver.ArrowLocation>(
|
||||
this, "arrowLocation", ArrowLocation.LEFT_TOP); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Stores the preferred arrow location. This might not be the actual
|
||||
* location of the arrow if auto fix is enabled.
|
||||
*
|
||||
* @see #setAutoFix(boolean)
|
||||
*
|
||||
* @return the arrow location property
|
||||
*/
|
||||
public final ObjectProperty<ArrowLocation> arrowLocationProperty() {
|
||||
return arrowLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the arrow location property.
|
||||
*
|
||||
* @see #arrowLocationProperty()
|
||||
*
|
||||
* @param location
|
||||
* the requested location
|
||||
*/
|
||||
public final void setArrowLocation(ArrowLocation location) {
|
||||
arrowLocationProperty().set(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the arrow location property.
|
||||
*
|
||||
* @see #arrowLocationProperty()
|
||||
*
|
||||
* @return the preferred arrow location
|
||||
*/
|
||||
public final ArrowLocation getArrowLocation() {
|
||||
return arrowLocationProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* All possible arrow locations.
|
||||
*/
|
||||
public enum ArrowLocation {
|
||||
LEFT_TOP, LEFT_CENTER, LEFT_BOTTOM, RIGHT_TOP, RIGHT_CENTER, RIGHT_BOTTOM, TOP_LEFT, TOP_CENTER, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
.popover {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.popover > .border {
|
||||
-fx-stroke: linear-gradient(to bottom, rgba(0,0,0, .3), rgba(0, 0, 0, .7)) ;
|
||||
-fx-stroke-width: 0.5;
|
||||
-fx-fill: rgba(255.0,255.0,255.0, .95);
|
||||
-fx-effect: dropshadow(gaussian, rgba(0,0,0,.2), 10.0, 0.5, 2.0, 2.0);
|
||||
}
|
||||
|
||||
.popover > .content {
|
||||
}
|
||||
|
||||
.popover > .detached {
|
||||
}
|
||||
|
||||
.popover > .content > .title > .text {
|
||||
-fx-padding: 6.0 6.0 0.0 6.0;
|
||||
-fx-text-fill: rgba(120, 120, 120, .8);
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.popover > .content > .title > .icon {
|
||||
-fx-padding: 6.0 0.0 0.0 10.0;
|
||||
}
|
||||
|
||||
.popover > .content > .title > .icon > .graphics > .circle {
|
||||
-fx-fill: gray ;
|
||||
-fx-effect: innershadow(gaussian, rgba(0,0,0,.2), 3, 0.5, 1.0, 1.0);
|
||||
}
|
||||
|
||||
.popover > .content > .title > .icon > .graphics > .line {
|
||||
-fx-stroke: white ;
|
||||
-fx-stroke-width: 2;
|
||||
}
|
@ -0,0 +1,693 @@
|
||||
/**
|
||||
* Copyright (c) 2013 - 2015, ControlsFX
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of ControlsFX, any associated website, nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package bisq.desktop.components.controlsfx.skin;
|
||||
|
||||
import static java.lang.Double.MAX_VALUE;
|
||||
import static javafx.geometry.Pos.CENTER_LEFT;
|
||||
import static javafx.scene.control.ContentDisplay.GRAPHIC_ONLY;
|
||||
import static bisq.desktop.components.controlsfx.control.PopOver.ArrowLocation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.shape.HLineTo;
|
||||
import javafx.scene.shape.Line;
|
||||
import javafx.scene.shape.LineTo;
|
||||
import javafx.scene.shape.MoveTo;
|
||||
import javafx.scene.shape.Path;
|
||||
import javafx.scene.shape.PathElement;
|
||||
import javafx.scene.shape.QuadCurveTo;
|
||||
import javafx.scene.shape.VLineTo;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import bisq.desktop.components.controlsfx.control.PopOver;
|
||||
import bisq.desktop.components.controlsfx.control.PopOver.ArrowLocation;
|
||||
|
||||
public class PopOverSkin implements Skin<PopOver> {
|
||||
|
||||
private static final String DETACHED_STYLE_CLASS = "detached"; //$NON-NLS-1$
|
||||
|
||||
private double xOffset;
|
||||
private double yOffset;
|
||||
|
||||
private boolean tornOff;
|
||||
|
||||
private Label title;
|
||||
private Label closeIcon;
|
||||
|
||||
private Path path;
|
||||
private BorderPane content;
|
||||
private StackPane titlePane;
|
||||
private StackPane stackPane;
|
||||
|
||||
private Point2D dragStartLocation;
|
||||
|
||||
private PopOver popOver;
|
||||
|
||||
public PopOverSkin(final PopOver popOver) {
|
||||
|
||||
this.popOver = popOver;
|
||||
|
||||
stackPane = new StackPane();
|
||||
stackPane.getStylesheets().add(
|
||||
PopOver.class.getResource("popover.css").toExternalForm()); //$NON-NLS-1$
|
||||
stackPane.setPickOnBounds(false);
|
||||
stackPane.getStyleClass().add("popover"); //$NON-NLS-1$
|
||||
|
||||
/*
|
||||
* The min width and height equal 2 * corner radius + 2 * arrow indent +
|
||||
* 2 * arrow size.
|
||||
*/
|
||||
stackPane.minWidthProperty().bind(
|
||||
Bindings.add(Bindings.multiply(2, popOver.arrowSizeProperty()),
|
||||
Bindings.add(
|
||||
Bindings.multiply(2,
|
||||
popOver.cornerRadiusProperty()),
|
||||
Bindings.multiply(2,
|
||||
popOver.arrowIndentProperty()))));
|
||||
|
||||
stackPane.minHeightProperty().bind(stackPane.minWidthProperty());
|
||||
|
||||
title = new Label();
|
||||
title.textProperty().bind(popOver.detachedTitleProperty());
|
||||
title.setMaxSize(MAX_VALUE, MAX_VALUE);
|
||||
title.setAlignment(Pos.CENTER);
|
||||
title.getStyleClass().add("text"); //$NON-NLS-1$
|
||||
|
||||
closeIcon = new Label();
|
||||
closeIcon.setGraphic(createCloseIcon());
|
||||
closeIcon.setMaxSize(MAX_VALUE, MAX_VALUE);
|
||||
closeIcon.setContentDisplay(GRAPHIC_ONLY);
|
||||
closeIcon.visibleProperty().bind(popOver.detachedProperty());
|
||||
closeIcon.getStyleClass().add("icon"); //$NON-NLS-1$
|
||||
closeIcon.setAlignment(CENTER_LEFT);
|
||||
closeIcon.getGraphic().setOnMouseClicked(
|
||||
new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent evt) {
|
||||
popOver.hide();
|
||||
}
|
||||
});
|
||||
|
||||
titlePane = new StackPane();
|
||||
titlePane.getChildren().add(title);
|
||||
titlePane.getChildren().add(closeIcon);
|
||||
titlePane.getStyleClass().add("title"); //$NON-NLS-1$
|
||||
|
||||
content = new BorderPane();
|
||||
content.setCenter(popOver.getContentNode());
|
||||
content.getStyleClass().add("content"); //$NON-NLS-1$
|
||||
|
||||
if (popOver.isDetached()) {
|
||||
content.setTop(titlePane);
|
||||
popOver.getStyleClass().add(DETACHED_STYLE_CLASS);
|
||||
content.getStyleClass().add(DETACHED_STYLE_CLASS);
|
||||
}
|
||||
|
||||
InvalidationListener updatePathListener = new InvalidationListener() {
|
||||
|
||||
@Override
|
||||
public void invalidated(Observable observable) {
|
||||
updatePath();
|
||||
}
|
||||
};
|
||||
|
||||
getPopupWindow().xProperty().addListener(updatePathListener);
|
||||
getPopupWindow().yProperty().addListener(updatePathListener);
|
||||
|
||||
popOver.arrowLocationProperty().addListener(updatePathListener);
|
||||
|
||||
popOver.contentNodeProperty().addListener(new ChangeListener<Node>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Node> value,
|
||||
Node oldContent, Node newContent) {
|
||||
content.setCenter(newContent);
|
||||
}
|
||||
});
|
||||
|
||||
popOver.detachedProperty().addListener(new ChangeListener<Boolean>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> value,
|
||||
Boolean oldDetached, Boolean newDetached) {
|
||||
|
||||
updatePath();
|
||||
|
||||
if (newDetached) {
|
||||
popOver.getStyleClass().add(DETACHED_STYLE_CLASS);
|
||||
content.getStyleClass().add(DETACHED_STYLE_CLASS);
|
||||
content.setTop(titlePane);
|
||||
} else {
|
||||
popOver.getStyleClass().remove(DETACHED_STYLE_CLASS);
|
||||
content.getStyleClass().remove(DETACHED_STYLE_CLASS);
|
||||
content.setTop(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
path = new Path();
|
||||
path.getStyleClass().add("border"); //$NON-NLS-1$
|
||||
path.setManaged(false);
|
||||
|
||||
createPathElements();
|
||||
updatePath();
|
||||
|
||||
final EventHandler<MouseEvent> mousePressedHandler = new EventHandler<MouseEvent>() {
|
||||
public void handle(MouseEvent evt) {
|
||||
if (popOver.isDetachable() || popOver.isDetached()) {
|
||||
tornOff = false;
|
||||
|
||||
xOffset = evt.getScreenX();
|
||||
yOffset = evt.getScreenY();
|
||||
|
||||
dragStartLocation = new Point2D(xOffset, yOffset);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
final EventHandler<MouseEvent> mouseReleasedHandler = new EventHandler<MouseEvent>() {
|
||||
public void handle(MouseEvent evt) {
|
||||
if (tornOff && !getSkinnable().isDetached()) {
|
||||
tornOff = false;
|
||||
getSkinnable().detach();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
final EventHandler<MouseEvent> mouseDragHandler = new EventHandler<MouseEvent>() {
|
||||
|
||||
public void handle(MouseEvent evt) {
|
||||
if (popOver.isDetachable() || popOver.isDetached()) {
|
||||
double deltaX = evt.getScreenX() - xOffset;
|
||||
double deltaY = evt.getScreenY() - yOffset;
|
||||
|
||||
Window window = getSkinnable().getScene().getWindow();
|
||||
|
||||
window.setX(window.getX() + deltaX);
|
||||
window.setY(window.getY() + deltaY);
|
||||
|
||||
xOffset = evt.getScreenX();
|
||||
yOffset = evt.getScreenY();
|
||||
|
||||
if (dragStartLocation.distance(xOffset, yOffset) > 20) {
|
||||
tornOff = true;
|
||||
updatePath();
|
||||
} else if (tornOff) {
|
||||
tornOff = false;
|
||||
updatePath();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
stackPane.setOnMousePressed(mousePressedHandler);
|
||||
stackPane.setOnMouseDragged(mouseDragHandler);
|
||||
stackPane.setOnMouseReleased(mouseReleasedHandler);
|
||||
|
||||
stackPane.getChildren().add(path);
|
||||
stackPane.getChildren().add(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getNode() {
|
||||
return stackPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PopOver getSkinnable() {
|
||||
return popOver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
}
|
||||
|
||||
private Node createCloseIcon() {
|
||||
Group group = new Group();
|
||||
group.getStyleClass().add("graphics"); //$NON-NLS-1$
|
||||
|
||||
Circle circle = new Circle();
|
||||
circle.getStyleClass().add("circle"); //$NON-NLS-1$
|
||||
circle.setRadius(6);
|
||||
circle.setCenterX(6);
|
||||
circle.setCenterY(6);
|
||||
group.getChildren().add(circle);
|
||||
|
||||
Line line1 = new Line();
|
||||
line1.getStyleClass().add("line"); //$NON-NLS-1$
|
||||
line1.setStartX(4);
|
||||
line1.setStartY(4);
|
||||
line1.setEndX(8);
|
||||
line1.setEndY(8);
|
||||
group.getChildren().add(line1);
|
||||
|
||||
Line line2 = new Line();
|
||||
line2.getStyleClass().add("line"); //$NON-NLS-1$
|
||||
line2.setStartX(8);
|
||||
line2.setStartY(4);
|
||||
line2.setEndX(4);
|
||||
line2.setEndY(8);
|
||||
group.getChildren().add(line2);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private MoveTo moveTo;
|
||||
|
||||
private QuadCurveTo topCurveTo, rightCurveTo, bottomCurveTo, leftCurveTo;
|
||||
|
||||
private HLineTo lineBTop, lineETop, lineHTop, lineKTop;
|
||||
private LineTo lineCTop, lineDTop, lineFTop, lineGTop, lineITop, lineJTop;
|
||||
|
||||
private VLineTo lineBRight, lineERight, lineHRight, lineKRight;
|
||||
private LineTo lineCRight, lineDRight, lineFRight, lineGRight, lineIRight,
|
||||
lineJRight;
|
||||
|
||||
private HLineTo lineBBottom, lineEBottom, lineHBottom, lineKBottom;
|
||||
private LineTo lineCBottom, lineDBottom, lineFBottom, lineGBottom,
|
||||
lineIBottom, lineJBottom;
|
||||
|
||||
private VLineTo lineBLeft, lineELeft, lineHLeft, lineKLeft;
|
||||
private LineTo lineCLeft, lineDLeft, lineFLeft, lineGLeft, lineILeft,
|
||||
lineJLeft;
|
||||
|
||||
private void createPathElements() {
|
||||
DoubleProperty centerYProperty = new SimpleDoubleProperty();
|
||||
DoubleProperty centerXProperty = new SimpleDoubleProperty();
|
||||
|
||||
DoubleProperty leftEdgeProperty = new SimpleDoubleProperty();
|
||||
DoubleProperty leftEdgePlusRadiusProperty = new SimpleDoubleProperty();
|
||||
|
||||
DoubleProperty topEdgeProperty = new SimpleDoubleProperty();
|
||||
DoubleProperty topEdgePlusRadiusProperty = new SimpleDoubleProperty();
|
||||
|
||||
DoubleProperty rightEdgeProperty = new SimpleDoubleProperty();
|
||||
DoubleProperty rightEdgeMinusRadiusProperty = new SimpleDoubleProperty();
|
||||
|
||||
DoubleProperty bottomEdgeProperty = new SimpleDoubleProperty();
|
||||
DoubleProperty bottomEdgeMinusRadiusProperty = new SimpleDoubleProperty();
|
||||
|
||||
DoubleProperty cornerProperty = getSkinnable().cornerRadiusProperty();
|
||||
|
||||
DoubleProperty arrowSizeProperty = getSkinnable().arrowSizeProperty();
|
||||
DoubleProperty arrowIndentProperty = getSkinnable()
|
||||
.arrowIndentProperty();
|
||||
|
||||
centerYProperty.bind(Bindings.divide(stackPane.heightProperty(), 2));
|
||||
centerXProperty.bind(Bindings.divide(stackPane.widthProperty(), 2));
|
||||
|
||||
leftEdgePlusRadiusProperty.bind(Bindings.add(leftEdgeProperty,
|
||||
getSkinnable().cornerRadiusProperty()));
|
||||
|
||||
topEdgePlusRadiusProperty.bind(Bindings.add(topEdgeProperty,
|
||||
getSkinnable().cornerRadiusProperty()));
|
||||
|
||||
rightEdgeProperty.bind(stackPane.widthProperty());
|
||||
rightEdgeMinusRadiusProperty.bind(Bindings.subtract(rightEdgeProperty,
|
||||
getSkinnable().cornerRadiusProperty()));
|
||||
|
||||
bottomEdgeProperty.bind(stackPane.heightProperty());
|
||||
bottomEdgeMinusRadiusProperty.bind(Bindings.subtract(
|
||||
bottomEdgeProperty, getSkinnable().cornerRadiusProperty()));
|
||||
|
||||
// INIT
|
||||
moveTo = new MoveTo();
|
||||
moveTo.xProperty().bind(leftEdgePlusRadiusProperty);
|
||||
moveTo.yProperty().bind(topEdgeProperty);
|
||||
|
||||
//
|
||||
// TOP EDGE
|
||||
//
|
||||
lineBTop = new HLineTo();
|
||||
lineBTop.xProperty().bind(
|
||||
Bindings.add(leftEdgePlusRadiusProperty, arrowIndentProperty));
|
||||
|
||||
lineCTop = new LineTo();
|
||||
lineCTop.xProperty().bind(
|
||||
Bindings.add(lineBTop.xProperty(), arrowSizeProperty));
|
||||
lineCTop.yProperty().bind(
|
||||
Bindings.subtract(topEdgeProperty, arrowSizeProperty));
|
||||
|
||||
lineDTop = new LineTo();
|
||||
lineDTop.xProperty().bind(
|
||||
Bindings.add(lineCTop.xProperty(), arrowSizeProperty));
|
||||
lineDTop.yProperty().bind(topEdgeProperty);
|
||||
|
||||
lineETop = new HLineTo();
|
||||
lineETop.xProperty().bind(
|
||||
Bindings.subtract(centerXProperty, arrowSizeProperty));
|
||||
|
||||
lineFTop = new LineTo();
|
||||
lineFTop.xProperty().bind(centerXProperty);
|
||||
lineFTop.yProperty().bind(
|
||||
Bindings.subtract(topEdgeProperty, arrowSizeProperty));
|
||||
|
||||
lineGTop = new LineTo();
|
||||
lineGTop.xProperty().bind(
|
||||
Bindings.add(centerXProperty, arrowSizeProperty));
|
||||
lineGTop.yProperty().bind(topEdgeProperty);
|
||||
|
||||
lineHTop = new HLineTo();
|
||||
lineHTop.xProperty().bind(
|
||||
Bindings.subtract(Bindings.subtract(
|
||||
rightEdgeMinusRadiusProperty, arrowIndentProperty),
|
||||
Bindings.multiply(arrowSizeProperty, 2)));
|
||||
|
||||
lineITop = new LineTo();
|
||||
lineITop.xProperty().bind(
|
||||
Bindings.subtract(Bindings.subtract(
|
||||
rightEdgeMinusRadiusProperty, arrowIndentProperty),
|
||||
arrowSizeProperty));
|
||||
lineITop.yProperty().bind(
|
||||
Bindings.subtract(topEdgeProperty, arrowSizeProperty));
|
||||
|
||||
lineJTop = new LineTo();
|
||||
lineJTop.xProperty().bind(
|
||||
Bindings.subtract(rightEdgeMinusRadiusProperty,
|
||||
arrowIndentProperty));
|
||||
lineJTop.yProperty().bind(topEdgeProperty);
|
||||
|
||||
lineKTop = new HLineTo();
|
||||
lineKTop.xProperty().bind(rightEdgeMinusRadiusProperty);
|
||||
|
||||
//
|
||||
// RIGHT EDGE
|
||||
//
|
||||
rightCurveTo = new QuadCurveTo();
|
||||
rightCurveTo.xProperty().bind(rightEdgeProperty);
|
||||
rightCurveTo.yProperty().bind(
|
||||
Bindings.add(topEdgeProperty, cornerProperty));
|
||||
rightCurveTo.controlXProperty().bind(rightEdgeProperty);
|
||||
rightCurveTo.controlYProperty().bind(topEdgeProperty);
|
||||
|
||||
lineBRight = new VLineTo();
|
||||
lineBRight.yProperty().bind(
|
||||
Bindings.add(topEdgePlusRadiusProperty, arrowIndentProperty));
|
||||
|
||||
lineCRight = new LineTo();
|
||||
lineCRight.xProperty().bind(
|
||||
Bindings.add(rightEdgeProperty, arrowSizeProperty));
|
||||
lineCRight.yProperty().bind(
|
||||
Bindings.add(lineBRight.yProperty(), arrowSizeProperty));
|
||||
|
||||
lineDRight = new LineTo();
|
||||
lineDRight.xProperty().bind(rightEdgeProperty);
|
||||
lineDRight.yProperty().bind(
|
||||
Bindings.add(lineCRight.yProperty(), arrowSizeProperty));
|
||||
|
||||
lineERight = new VLineTo();
|
||||
lineERight.yProperty().bind(
|
||||
Bindings.subtract(centerYProperty, arrowSizeProperty));
|
||||
|
||||
lineFRight = new LineTo();
|
||||
lineFRight.xProperty().bind(
|
||||
Bindings.add(rightEdgeProperty, arrowSizeProperty));
|
||||
lineFRight.yProperty().bind(centerYProperty);
|
||||
|
||||
lineGRight = new LineTo();
|
||||
lineGRight.xProperty().bind(rightEdgeProperty);
|
||||
lineGRight.yProperty().bind(
|
||||
Bindings.add(centerYProperty, arrowSizeProperty));
|
||||
|
||||
lineHRight = new VLineTo();
|
||||
lineHRight.yProperty().bind(
|
||||
Bindings.subtract(Bindings.subtract(
|
||||
bottomEdgeMinusRadiusProperty, arrowIndentProperty),
|
||||
Bindings.multiply(arrowSizeProperty, 2)));
|
||||
|
||||
lineIRight = new LineTo();
|
||||
lineIRight.xProperty().bind(
|
||||
Bindings.add(rightEdgeProperty, arrowSizeProperty));
|
||||
lineIRight.yProperty().bind(
|
||||
Bindings.subtract(Bindings.subtract(
|
||||
bottomEdgeMinusRadiusProperty, arrowIndentProperty),
|
||||
arrowSizeProperty));
|
||||
|
||||
lineJRight = new LineTo();
|
||||
lineJRight.xProperty().bind(rightEdgeProperty);
|
||||
lineJRight.yProperty().bind(
|
||||
Bindings.subtract(bottomEdgeMinusRadiusProperty,
|
||||
arrowIndentProperty));
|
||||
|
||||
lineKRight = new VLineTo();
|
||||
lineKRight.yProperty().bind(bottomEdgeMinusRadiusProperty);
|
||||
|
||||
//
|
||||
// BOTTOM EDGE
|
||||
//
|
||||
|
||||
bottomCurveTo = new QuadCurveTo();
|
||||
bottomCurveTo.xProperty().bind(rightEdgeMinusRadiusProperty);
|
||||
bottomCurveTo.yProperty().bind(bottomEdgeProperty);
|
||||
bottomCurveTo.controlXProperty().bind(rightEdgeProperty);
|
||||
bottomCurveTo.controlYProperty().bind(bottomEdgeProperty);
|
||||
|
||||
lineBBottom = new HLineTo();
|
||||
lineBBottom.xProperty().bind(
|
||||
Bindings.subtract(rightEdgeMinusRadiusProperty,
|
||||
arrowIndentProperty));
|
||||
|
||||
lineCBottom = new LineTo();
|
||||
lineCBottom.xProperty().bind(
|
||||
Bindings.subtract(lineBBottom.xProperty(), arrowSizeProperty));
|
||||
lineCBottom.yProperty().bind(
|
||||
Bindings.add(bottomEdgeProperty, arrowSizeProperty));
|
||||
|
||||
lineDBottom = new LineTo();
|
||||
lineDBottom.xProperty().bind(
|
||||
Bindings.subtract(lineCBottom.xProperty(), arrowSizeProperty));
|
||||
lineDBottom.yProperty().bind(bottomEdgeProperty);
|
||||
|
||||
lineEBottom = new HLineTo();
|
||||
lineEBottom.xProperty().bind(
|
||||
Bindings.add(centerXProperty, arrowSizeProperty));
|
||||
|
||||
lineFBottom = new LineTo();
|
||||
lineFBottom.xProperty().bind(centerXProperty);
|
||||
lineFBottom.yProperty().bind(
|
||||
Bindings.add(bottomEdgeProperty, arrowSizeProperty));
|
||||
|
||||
lineGBottom = new LineTo();
|
||||
lineGBottom.xProperty().bind(
|
||||
Bindings.subtract(centerXProperty, arrowSizeProperty));
|
||||
lineGBottom.yProperty().bind(bottomEdgeProperty);
|
||||
|
||||
lineHBottom = new HLineTo();
|
||||
lineHBottom.xProperty().bind(
|
||||
Bindings.add(Bindings.add(leftEdgePlusRadiusProperty,
|
||||
arrowIndentProperty), Bindings.multiply(
|
||||
arrowSizeProperty, 2)));
|
||||
|
||||
lineIBottom = new LineTo();
|
||||
lineIBottom.xProperty().bind(
|
||||
Bindings.add(Bindings.add(leftEdgePlusRadiusProperty,
|
||||
arrowIndentProperty), arrowSizeProperty));
|
||||
lineIBottom.yProperty().bind(
|
||||
Bindings.add(bottomEdgeProperty, arrowSizeProperty));
|
||||
|
||||
lineJBottom = new LineTo();
|
||||
lineJBottom.xProperty().bind(
|
||||
Bindings.add(leftEdgePlusRadiusProperty, arrowIndentProperty));
|
||||
lineJBottom.yProperty().bind(bottomEdgeProperty);
|
||||
|
||||
lineKBottom = new HLineTo();
|
||||
lineKBottom.xProperty().bind(leftEdgePlusRadiusProperty);
|
||||
|
||||
//
|
||||
// LEFT EDGE
|
||||
//
|
||||
leftCurveTo = new QuadCurveTo();
|
||||
leftCurveTo.xProperty().bind(leftEdgeProperty);
|
||||
leftCurveTo.yProperty().bind(
|
||||
Bindings.subtract(bottomEdgeProperty, cornerProperty));
|
||||
leftCurveTo.controlXProperty().bind(leftEdgeProperty);
|
||||
leftCurveTo.controlYProperty().bind(bottomEdgeProperty);
|
||||
|
||||
lineBLeft = new VLineTo();
|
||||
lineBLeft.yProperty().bind(
|
||||
Bindings.subtract(bottomEdgeMinusRadiusProperty,
|
||||
arrowIndentProperty));
|
||||
|
||||
lineCLeft = new LineTo();
|
||||
lineCLeft.xProperty().bind(
|
||||
Bindings.subtract(leftEdgeProperty, arrowSizeProperty));
|
||||
lineCLeft.yProperty().bind(
|
||||
Bindings.subtract(lineBLeft.yProperty(), arrowSizeProperty));
|
||||
|
||||
lineDLeft = new LineTo();
|
||||
lineDLeft.xProperty().bind(leftEdgeProperty);
|
||||
lineDLeft.yProperty().bind(
|
||||
Bindings.subtract(lineCLeft.yProperty(), arrowSizeProperty));
|
||||
|
||||
lineELeft = new VLineTo();
|
||||
lineELeft.yProperty().bind(
|
||||
Bindings.add(centerYProperty, arrowSizeProperty));
|
||||
|
||||
lineFLeft = new LineTo();
|
||||
lineFLeft.xProperty().bind(
|
||||
Bindings.subtract(leftEdgeProperty, arrowSizeProperty));
|
||||
lineFLeft.yProperty().bind(centerYProperty);
|
||||
|
||||
lineGLeft = new LineTo();
|
||||
lineGLeft.xProperty().bind(leftEdgeProperty);
|
||||
lineGLeft.yProperty().bind(
|
||||
Bindings.subtract(centerYProperty, arrowSizeProperty));
|
||||
|
||||
lineHLeft = new VLineTo();
|
||||
lineHLeft.yProperty().bind(
|
||||
Bindings.add(Bindings.add(topEdgePlusRadiusProperty,
|
||||
arrowIndentProperty), Bindings.multiply(
|
||||
arrowSizeProperty, 2)));
|
||||
|
||||
lineILeft = new LineTo();
|
||||
lineILeft.xProperty().bind(
|
||||
Bindings.subtract(leftEdgeProperty, arrowSizeProperty));
|
||||
lineILeft.yProperty().bind(
|
||||
Bindings.add(Bindings.add(topEdgePlusRadiusProperty,
|
||||
arrowIndentProperty), arrowSizeProperty));
|
||||
|
||||
lineJLeft = new LineTo();
|
||||
lineJLeft.xProperty().bind(leftEdgeProperty);
|
||||
lineJLeft.yProperty().bind(
|
||||
Bindings.add(topEdgePlusRadiusProperty, arrowIndentProperty));
|
||||
|
||||
lineKLeft = new VLineTo();
|
||||
lineKLeft.yProperty().bind(topEdgePlusRadiusProperty);
|
||||
|
||||
topCurveTo = new QuadCurveTo();
|
||||
topCurveTo.xProperty().bind(leftEdgePlusRadiusProperty);
|
||||
topCurveTo.yProperty().bind(topEdgeProperty);
|
||||
topCurveTo.controlXProperty().bind(leftEdgeProperty);
|
||||
topCurveTo.controlYProperty().bind(topEdgeProperty);
|
||||
}
|
||||
|
||||
private Window getPopupWindow() {
|
||||
return getSkinnable().getScene().getWindow();
|
||||
}
|
||||
|
||||
private boolean showArrow(ArrowLocation location) {
|
||||
ArrowLocation arrowLocation = getSkinnable().getArrowLocation();
|
||||
return location.equals(arrowLocation) && !getSkinnable().isDetached()
|
||||
&& !tornOff;
|
||||
}
|
||||
|
||||
private void updatePath() {
|
||||
List<PathElement> elements = new ArrayList<>();
|
||||
elements.add(moveTo);
|
||||
|
||||
if (showArrow(TOP_LEFT)) {
|
||||
elements.add(lineBTop);
|
||||
elements.add(lineCTop);
|
||||
elements.add(lineDTop);
|
||||
}
|
||||
if (showArrow(TOP_CENTER)) {
|
||||
elements.add(lineETop);
|
||||
elements.add(lineFTop);
|
||||
elements.add(lineGTop);
|
||||
}
|
||||
if (showArrow(TOP_RIGHT)) {
|
||||
elements.add(lineHTop);
|
||||
elements.add(lineITop);
|
||||
elements.add(lineJTop);
|
||||
}
|
||||
elements.add(lineKTop);
|
||||
elements.add(rightCurveTo);
|
||||
|
||||
if (showArrow(RIGHT_TOP)) {
|
||||
elements.add(lineBRight);
|
||||
elements.add(lineCRight);
|
||||
elements.add(lineDRight);
|
||||
}
|
||||
if (showArrow(RIGHT_CENTER)) {
|
||||
elements.add(lineERight);
|
||||
elements.add(lineFRight);
|
||||
elements.add(lineGRight);
|
||||
}
|
||||
if (showArrow(RIGHT_BOTTOM)) {
|
||||
elements.add(lineHRight);
|
||||
elements.add(lineIRight);
|
||||
elements.add(lineJRight);
|
||||
}
|
||||
elements.add(lineKRight);
|
||||
elements.add(bottomCurveTo);
|
||||
|
||||
if (showArrow(BOTTOM_RIGHT)) {
|
||||
elements.add(lineBBottom);
|
||||
elements.add(lineCBottom);
|
||||
elements.add(lineDBottom);
|
||||
}
|
||||
if (showArrow(BOTTOM_CENTER)) {
|
||||
elements.add(lineEBottom);
|
||||
elements.add(lineFBottom);
|
||||
elements.add(lineGBottom);
|
||||
}
|
||||
if (showArrow(BOTTOM_LEFT)) {
|
||||
elements.add(lineHBottom);
|
||||
elements.add(lineIBottom);
|
||||
elements.add(lineJBottom);
|
||||
}
|
||||
elements.add(lineKBottom);
|
||||
elements.add(leftCurveTo);
|
||||
|
||||
if (showArrow(LEFT_BOTTOM)) {
|
||||
elements.add(lineBLeft);
|
||||
elements.add(lineCLeft);
|
||||
elements.add(lineDLeft);
|
||||
}
|
||||
if (showArrow(LEFT_CENTER)) {
|
||||
elements.add(lineELeft);
|
||||
elements.add(lineFLeft);
|
||||
elements.add(lineGLeft);
|
||||
}
|
||||
if (showArrow(LEFT_TOP)) {
|
||||
elements.add(lineHLeft);
|
||||
elements.add(lineILeft);
|
||||
elements.add(lineJLeft);
|
||||
}
|
||||
elements.add(lineKLeft);
|
||||
elements.add(topCurveTo);
|
||||
|
||||
path.getElements().setAll(elements);
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import bisq.desktop.components.AutoTooltipCheckBox;
|
||||
import bisq.desktop.components.InfoTextField;
|
||||
import bisq.desktop.components.InputTextField;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
import bisq.desktop.util.Layout;
|
||||
|
||||
@ -182,7 +183,7 @@ public abstract class PaymentMethodForm {
|
||||
Res.get("payment.maxPeriodAndLimit",
|
||||
getTimeText(hours),
|
||||
formatter.formatCoinWithCode(Coin.valueOf(accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrency.getCode()))),
|
||||
formatter.formatAccountAge(accountAge));
|
||||
DisplayUtils.formatAccountAge(accountAge));
|
||||
|
||||
if (isDisplayForm)
|
||||
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.limitations"), limitationsText);
|
||||
|
@ -38,6 +38,7 @@ import bisq.desktop.main.portfolio.PortfolioView;
|
||||
import bisq.desktop.main.settings.SettingsView;
|
||||
import bisq.desktop.main.shared.PriceFeedComboBoxItem;
|
||||
import bisq.desktop.main.support.SupportView;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.Transitions;
|
||||
|
||||
import bisq.core.dao.monitoring.DaoStateMonitoringService;
|
||||
@ -45,6 +46,7 @@ import bisq.core.exceptions.BisqException;
|
||||
import bisq.core.locale.GlobalSettings;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.locale.LanguageUtil;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
@ -82,6 +84,7 @@ import javafx.scene.text.TextAlignment;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
@ -173,6 +176,8 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
|
||||
@Override
|
||||
protected void initialize() {
|
||||
MainView.rootContainer = root;
|
||||
if (LanguageUtil.isDefaultLanguageRTL())
|
||||
MainView.rootContainer.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
|
||||
|
||||
ToggleButton marketButton = new NavButton(MarketView.class, Res.get("mainView.menu.market").toUpperCase());
|
||||
ToggleButton buyButton = new NavButton(BuyOfferView.class, Res.get("mainView.menu.buyBtc").toUpperCase());
|
||||
@ -523,14 +528,14 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
|
||||
res = Res.get("mainView.marketPrice.tooltip",
|
||||
"https://bitcoinaverage.com",
|
||||
"",
|
||||
formatter.formatTime(model.getPriceFeedService().getLastRequestTimeStampBtcAverage()),
|
||||
DisplayUtils.formatTime(model.getPriceFeedService().getLastRequestTimeStampBtcAverage()),
|
||||
model.getPriceFeedService().getProviderNodeAddress());
|
||||
} else {
|
||||
String altcoinExtra = "\n" + Res.get("mainView.marketPrice.tooltip.altcoinExtra");
|
||||
res = Res.get("mainView.marketPrice.tooltip",
|
||||
"https://poloniex.com",
|
||||
altcoinExtra,
|
||||
formatter.formatTime(model.getPriceFeedService().getLastRequestTimeStampPoloniex()),
|
||||
DisplayUtils.formatTime(model.getPriceFeedService().getLastRequestTimeStampPoloniex()),
|
||||
model.getPriceFeedService().getProviderNodeAddress());
|
||||
}
|
||||
return res;
|
||||
|
@ -33,6 +33,7 @@ import bisq.desktop.main.overlays.windows.downloadupdate.DisplayUpdateDownloadWi
|
||||
import bisq.desktop.main.presentation.DaoPresentation;
|
||||
import bisq.desktop.main.presentation.MarketPricePresentation;
|
||||
import bisq.desktop.main.shared.PriceFeedComboBoxItem;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
@ -218,7 +219,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupCompleteList
|
||||
DontShowAgainLookup.dontShowAgain(key, true);
|
||||
new Popup<>().warning(Res.get("popup.warning.tradePeriod.halfReached",
|
||||
trade.getShortId(),
|
||||
formatter.formatDateTime(maxTradePeriodDate)))
|
||||
DisplayUtils.formatDateTime(maxTradePeriodDate)))
|
||||
.show();
|
||||
}
|
||||
break;
|
||||
@ -228,7 +229,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupCompleteList
|
||||
DontShowAgainLookup.dontShowAgain(key, true);
|
||||
new Popup<>().warning(Res.get("popup.warning.tradePeriod.ended",
|
||||
trade.getShortId(),
|
||||
formatter.formatDateTime(maxTradePeriodDate)))
|
||||
DisplayUtils.formatDateTime(maxTradePeriodDate)))
|
||||
.show();
|
||||
}
|
||||
break;
|
||||
|
@ -140,7 +140,7 @@ public class ManageMarketAlertsWindow extends Overlay<ManageMarketAlertsWindow>
|
||||
public void updateItem(final MarketAlertFilter item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
setText(formatter.formatPercentagePrice(item.getTriggerValue() / 10000d));
|
||||
setText(BSFormatter.formatPercentagePrice(item.getTriggerValue() / 10000d));
|
||||
} else {
|
||||
setText("");
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.util.validation.InputValidator;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
@ -347,7 +348,7 @@ public class MobileNotificationsView extends ActivatableView<GridPane, Void> {
|
||||
|
||||
private void onAddMarketAlert() {
|
||||
PaymentAccount paymentAccount = paymentAccountsComboBox.getSelectionModel().getSelectedItem();
|
||||
double percentAsDouble = formatter.parsePercentStringToDouble(marketAlertTriggerInputTextField.getText());
|
||||
double percentAsDouble = ParsingUtils.parsePercentStringToDouble(marketAlertTriggerInputTextField.getText());
|
||||
int triggerValue = (int) Math.round(percentAsDouble * 10000);
|
||||
boolean isBuyOffer = offerTypeRadioButtonsToggleGroup.getSelectedToggle() == buyOffersRadioButton;
|
||||
MarketAlertFilter marketAlertFilter = new MarketAlertFilter(paymentAccount, triggerValue, isBuyOffer);
|
||||
@ -510,8 +511,8 @@ public class MobileNotificationsView extends ActivatableView<GridPane, Void> {
|
||||
marketAlertTriggerFocusListener = (observable, oldValue, newValue) -> {
|
||||
if (oldValue && !newValue) {
|
||||
try {
|
||||
double percentAsDouble = formatter.parsePercentStringToDouble(marketAlertTriggerInputTextField.getText()) * 100;
|
||||
marketAlertTriggerInputTextField.setText(formatter.formatRoundedDoubleWithPrecision(percentAsDouble, 2) + "%");
|
||||
double percentAsDouble = ParsingUtils.parsePercentStringToDouble(marketAlertTriggerInputTextField.getText()) * 100;
|
||||
marketAlertTriggerInputTextField.setText(BSFormatter.formatRoundedDoubleWithPrecision(percentAsDouble, 2) + "%");
|
||||
} catch (Throwable ignore) {
|
||||
}
|
||||
|
||||
@ -695,8 +696,8 @@ public class MobileNotificationsView extends ActivatableView<GridPane, Void> {
|
||||
currencyComboBox.getSelectionModel().select(optionalTradeCurrency.get());
|
||||
onSelectedTradeCurrency();
|
||||
|
||||
priceAlertHighInputTextField.setText(formatter.formatMarketPrice(priceAlertFilter.getHigh() / 10000d, currencyCode));
|
||||
priceAlertLowInputTextField.setText(formatter.formatMarketPrice(priceAlertFilter.getLow() / 10000d, currencyCode));
|
||||
priceAlertHighInputTextField.setText(BSFormatter.formatMarketPrice(priceAlertFilter.getHigh() / 10000d, currencyCode));
|
||||
priceAlertLowInputTextField.setText(BSFormatter.formatMarketPrice(priceAlertFilter.getLow() / 10000d, currencyCode));
|
||||
} else {
|
||||
currencyComboBox.getSelectionModel().clearSelection();
|
||||
}
|
||||
@ -747,15 +748,15 @@ public class MobileNotificationsView extends ActivatableView<GridPane, Void> {
|
||||
try {
|
||||
String inputValue = inputTextField.getText();
|
||||
if (inputValue != null && !inputValue.isEmpty() && selectedPriceAlertTradeCurrency != null) {
|
||||
double priceAsDouble = formatter.parseNumberStringToDouble(inputValue);
|
||||
double priceAsDouble = ParsingUtils.parseNumberStringToDouble(inputValue);
|
||||
String currencyCode = selectedPriceAlertTradeCurrency;
|
||||
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT : 2;
|
||||
// We want to use the converted value not the inout value as we apply the converted value at focus out.
|
||||
// E.g. if input is 5555.5555 it will be rounded to 5555.55 and we use that as the value for comparing
|
||||
// low and high price...
|
||||
String stringValue = formatter.formatRoundedDoubleWithPrecision(priceAsDouble, precision);
|
||||
return formatter.parsePriceStringToLong(currencyCode, stringValue, precision);
|
||||
String stringValue = BSFormatter.formatRoundedDoubleWithPrecision(priceAsDouble, precision);
|
||||
return ParsingUtils.parsePriceStringToLong(currencyCode, stringValue, precision);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@ -768,11 +769,11 @@ public class MobileNotificationsView extends ActivatableView<GridPane, Void> {
|
||||
try {
|
||||
String inputValue = inputTextField.getText();
|
||||
if (inputValue != null && !inputValue.isEmpty() && selectedPriceAlertTradeCurrency != null) {
|
||||
double priceAsDouble = formatter.parseNumberStringToDouble(inputValue);
|
||||
double priceAsDouble = ParsingUtils.parseNumberStringToDouble(inputValue);
|
||||
String currencyCode = selectedPriceAlertTradeCurrency;
|
||||
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT : 2;
|
||||
String stringValue = formatter.formatRoundedDoubleWithPrecision(priceAsDouble, precision);
|
||||
String stringValue = BSFormatter.formatRoundedDoubleWithPrecision(priceAsDouble, precision);
|
||||
inputTextField.setText(stringValue);
|
||||
}
|
||||
} catch (Throwable ignore) {
|
||||
|
@ -113,7 +113,7 @@ public class BondingViewUtils {
|
||||
Coin miningFee = miningFeeAndTxSize.first;
|
||||
int txSize = miningFeeAndTxSize.second;
|
||||
BSFormatter formatter = new BSFormatter();
|
||||
String duration = formatter.formatDurationAsWords(lockupTime * 10 * 60 * 1000L, false, false);
|
||||
String duration = BSFormatter.formatDurationAsWords(lockupTime * 10 * 60 * 1000L, false, false);
|
||||
new Popup<>().headLine(Res.get("dao.bond.reputation.lockup.headline"))
|
||||
.confirmation(Res.get("dao.bond.reputation.lockup.details",
|
||||
bsqFormatter.formatCoinWithCode(lockupAmount),
|
||||
@ -172,7 +172,7 @@ public class BondingViewUtils {
|
||||
Coin miningFee = miningFeeAndTxSize.first;
|
||||
int txSize = miningFeeAndTxSize.second;
|
||||
BSFormatter formatter = new BSFormatter();
|
||||
String duration = formatter.formatDurationAsWords(lockTime * 10 * 60 * 1000L, false, false);
|
||||
String duration = BSFormatter.formatDurationAsWords(lockTime * 10 * 60 * 1000L, false, false);
|
||||
new Popup<>().headLine(Res.get("dao.bond.reputation.unlock.headline"))
|
||||
.confirmation(Res.get("dao.bond.reputation.unlock.details",
|
||||
bsqFormatter.formatCoinWithCode(unlockAmount),
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
package bisq.desktop.main.dao.bonding.bonds;
|
||||
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
|
||||
import bisq.core.dao.governance.bond.Bond;
|
||||
import bisq.core.dao.governance.bond.BondState;
|
||||
import bisq.core.dao.governance.bond.role.BondedRole;
|
||||
@ -58,7 +60,7 @@ class BondListItem {
|
||||
bondDetails = Utilities.bytesAsHexString(bond.getBondedAsset().getHash());
|
||||
}
|
||||
lockupTxId = bond.getLockupTxId();
|
||||
lockupDateString = bond.getLockupDate() > 0 ? bsqFormatter.formatDateTime(new Date(bond.getLockupDate())) : "-";
|
||||
lockupDateString = bond.getLockupDate() > 0 ? DisplayUtils.formatDateTime(new Date(bond.getLockupDate())) : "-";
|
||||
bondState = bond.getBondState();
|
||||
bondStateString = Res.get("dao.bond.bondState." + bond.getBondState().name());
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
package bisq.desktop.main.dao.bonding.reputation;
|
||||
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
|
||||
import bisq.core.dao.governance.bond.BondState;
|
||||
import bisq.core.dao.governance.bond.reputation.MyBondedReputation;
|
||||
import bisq.core.dao.governance.bond.reputation.MyReputation;
|
||||
@ -57,7 +59,7 @@ class MyReputationListItem {
|
||||
txId = myBondedReputation.getLockupTxId();
|
||||
amount = bsqFormatter.formatCoin(Coin.valueOf(myBondedReputation.getAmount()));
|
||||
lockupDate = new Date(myBondedReputation.getLockupDate());
|
||||
lockupDateString = bsqFormatter.formatDateTime(lockupDate);
|
||||
lockupDateString = DisplayUtils.formatDateTime(lockupDate);
|
||||
lockTime = Integer.toString(myBondedReputation.getLockTime());
|
||||
lockupTxId = myBondedReputation.getLockupTxId();
|
||||
bondState = myBondedReputation.getBondState();
|
||||
|
@ -38,6 +38,7 @@ import bisq.core.dao.governance.bond.reputation.MyBondedReputation;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.BsqFormatter;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.util.validation.HexStringValidator;
|
||||
import bisq.core.util.validation.IntegerValidator;
|
||||
|
||||
@ -170,7 +171,7 @@ public class MyReputationView extends ActivatableView<GridPane, Void> implements
|
||||
bsqWalletService.addBsqBalanceListener(this);
|
||||
|
||||
lockupButton.setOnAction((event) -> {
|
||||
Coin lockupAmount = bsqFormatter.parseToCoin(amountInputTextField.getText());
|
||||
Coin lockupAmount = ParsingUtils.parseToCoin(amountInputTextField.getText(), bsqFormatter);
|
||||
int lockupTime = Integer.parseInt(timeInputTextField.getText());
|
||||
byte[] salt = Utilities.decodeFromHex(saltInputTextField.getText());
|
||||
bondingViewUtils.lockupBondForReputation(lockupAmount,
|
||||
|
@ -18,6 +18,7 @@
|
||||
package bisq.desktop.main.dao.bonding.roles;
|
||||
|
||||
import bisq.desktop.main.overlays.Overlay;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
|
||||
import bisq.core.dao.DaoFacade;
|
||||
@ -97,6 +98,6 @@ class RoleDetailsWindow extends Overlay<RoleDetailsWindow> {
|
||||
bondedRoleType.getLink(), bondedRoleType.getLink(), 0);
|
||||
|
||||
FormBuilder.addTopLabelTextField(gridPane, ++rowIndex, Res.get("dao.bond.details.isSingleton"),
|
||||
bsqFormatter.booleanToYesNo(bondedRoleType.isAllowMultipleHolders()));
|
||||
DisplayUtils.booleanToYesNo(bondedRoleType.isAllowMultipleHolders()));
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.BsqFormatter;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import bisq.common.app.DevEnv;
|
||||
|
||||
@ -273,7 +274,7 @@ public class AssetFeeView extends ActivatableView<GridPane, Void> implements Bsq
|
||||
}
|
||||
|
||||
private Coin getListingFee() {
|
||||
return bsqFormatter.parseToCoin(feeAmountInputTextField.getText());
|
||||
return ParsingUtils.parseToCoin(feeAmountInputTextField.getText(), bsqFormatter);
|
||||
}
|
||||
|
||||
private void doPublishFeeTx(Transaction transaction) {
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
package bisq.desktop.main.dao.burnbsq.proofofburn;
|
||||
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
|
||||
import bisq.core.dao.governance.proofofburn.MyProofOfBurn;
|
||||
import bisq.core.dao.governance.proofofburn.ProofOfBurnService;
|
||||
import bisq.core.dao.state.model.blockchain.Tx;
|
||||
@ -52,7 +54,7 @@ class MyProofOfBurnListItem {
|
||||
if (optionalTx.isPresent()) {
|
||||
Tx tx = optionalTx.get();
|
||||
date = new Date(tx.getTime());
|
||||
dateAsString = bsqFormatter.formatDateTime(date);
|
||||
dateAsString = DisplayUtils.formatDateTime(date);
|
||||
amount = proofOfBurnService.getAmount(tx);
|
||||
amountAsString = bsqFormatter.formatCoinWithCode(Coin.valueOf(amount));
|
||||
txId = tx.getId();
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
package bisq.desktop.main.dao.burnbsq.proofofburn;
|
||||
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
|
||||
import bisq.core.dao.governance.proofofburn.ProofOfBurnService;
|
||||
import bisq.core.dao.state.model.blockchain.Tx;
|
||||
import bisq.core.util.BsqFormatter;
|
||||
@ -46,6 +48,6 @@ class ProofOfBurnListItem {
|
||||
hashAsHex = Utilities.bytesAsHexString(proofOfBurnService.getHashFromOpReturnData(tx));
|
||||
pubKey = Utilities.bytesAsHexString(proofOfBurnService.getPubKey(txId));
|
||||
date = new Date(tx.getTime());
|
||||
dateAsString = bsqFormatter.formatDateTime(date);
|
||||
dateAsString = DisplayUtils.formatDateTime(date);
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import bisq.core.locale.Res;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.BsqFormatter;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.util.validation.InputValidator;
|
||||
|
||||
import bisq.common.app.DevEnv;
|
||||
@ -273,7 +274,7 @@ public class ProofOfBurnView extends ActivatableView<GridPane, Void> implements
|
||||
}
|
||||
|
||||
private Coin getAmountFee() {
|
||||
return bsqFormatter.parseToCoin(amountInputTextField.getText());
|
||||
return ParsingUtils.parseToCoin(amountInputTextField.getText(), bsqFormatter);
|
||||
}
|
||||
|
||||
private void doPublishFeeTx(Transaction transaction, String preImageAsString) {
|
||||
|
@ -34,6 +34,7 @@ import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.statistics.TradeStatistics2;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.BsqFormatter;
|
||||
|
||||
import bisq.common.util.MathUtils;
|
||||
@ -321,7 +322,7 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
|
||||
Optional<Price> optionalBsqPrice = priceFeedService.getBsqPrice();
|
||||
if (optionalBsqPrice.isPresent()) {
|
||||
Price bsqPrice = optionalBsqPrice.get();
|
||||
marketPriceLabel.setText(bsqFormatter.formatPrice(bsqPrice) + " BSQ/BTC");
|
||||
marketPriceLabel.setText(BSFormatter.formatPrice(bsqPrice) + " BSQ/BTC");
|
||||
|
||||
marketCapTextField.setText(bsqFormatter.formatMarketCap(priceFeedService.getMarketPrice("BSQ"),
|
||||
priceFeedService.getMarketPrice(preferences.getPreferredTradeCurrency().getCode()),
|
||||
|
@ -52,6 +52,7 @@ import bisq.core.dao.state.model.governance.Role;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.BsqFormatter;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import bisq.asset.Asset;
|
||||
|
||||
@ -395,13 +396,13 @@ public class MakeProposalView extends ActivatableView<GridPane, Void> implements
|
||||
"proposalDisplay.requestedBsqTextField must not be null");
|
||||
return daoFacade.getCompensationProposalWithTransaction(name,
|
||||
link,
|
||||
bsqFormatter.parseToCoin(proposalDisplay.requestedBsqTextField.getText()));
|
||||
ParsingUtils.parseToCoin(proposalDisplay.requestedBsqTextField.getText(), bsqFormatter));
|
||||
case REIMBURSEMENT_REQUEST:
|
||||
checkNotNull(proposalDisplay.requestedBsqTextField,
|
||||
"proposalDisplay.requestedBsqTextField must not be null");
|
||||
return daoFacade.getReimbursementProposalWithTransaction(name,
|
||||
link,
|
||||
bsqFormatter.parseToCoin(proposalDisplay.requestedBsqTextField.getText()));
|
||||
ParsingUtils.parseToCoin(proposalDisplay.requestedBsqTextField.getText(), bsqFormatter));
|
||||
case CHANGE_PARAM:
|
||||
checkNotNull(proposalDisplay.paramComboBox,
|
||||
"proposalDisplay.paramComboBox must not be null");
|
||||
|
@ -30,6 +30,7 @@ import bisq.desktop.components.TxIdTextField;
|
||||
import bisq.desktop.main.dao.governance.PhasesView;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.main.overlays.windows.SelectProposalWindow;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
import bisq.desktop.util.Layout;
|
||||
import bisq.desktop.util.validation.BsqValidator;
|
||||
@ -54,6 +55,7 @@ import bisq.core.locale.Res;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.BsqFormatter;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.DevEnv;
|
||||
@ -462,7 +464,7 @@ public class ProposalsView extends ActivatableView<GridPane, Void> implements Bs
|
||||
}
|
||||
|
||||
private void onVote() {
|
||||
Coin stake = bsqFormatter.parseToCoin(stakeInputTextField.getText());
|
||||
Coin stake = ParsingUtils.parseToCoin(stakeInputTextField.getText(), bsqFormatter);
|
||||
try {
|
||||
// We create a dummy tx to get the miningFee for displaying it at the confirmation popup
|
||||
Tuple2<Coin, Integer> miningFeeAndTxSize = daoFacade.getBlindVoteMiningFeeAndTxSize(stake);
|
||||
@ -718,7 +720,7 @@ public class ProposalsView extends ActivatableView<GridPane, Void> implements Bs
|
||||
public void updateItem(final ProposalsListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(bsqFormatter.formatDateTime(item.getProposal().getCreationDateAsDate()));
|
||||
setText(DisplayUtils.formatDateTime(item.getProposal().getCreationDateAsDate()));
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import bisq.desktop.components.TableGroupHeadline;
|
||||
import bisq.desktop.main.dao.governance.PhasesView;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.main.overlays.windows.ProposalResultsWindow;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
import bisq.desktop.util.Layout;
|
||||
@ -637,7 +638,7 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
|
||||
public void updateItem(final ProposalListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(bsqFormatter.formatDateTime(item.getProposal().getCreationDateAsDate()));
|
||||
setText(DisplayUtils.formatDateTime(item.getProposal().getCreationDateAsDate()));
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ import bisq.core.locale.Res;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.BsqFormatter;
|
||||
import bisq.core.util.CoinUtil;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.util.validation.BtcAddressValidator;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
@ -234,7 +235,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
||||
// TODO break up in methods
|
||||
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
|
||||
String receiversAddressString = bsqFormatter.getAddressFromBsqAddress(receiversAddressInputTextField.getText()).toString();
|
||||
Coin receiverAmount = bsqFormatter.parseToCoin(amountInputTextField.getText());
|
||||
Coin receiverAmount = ParsingUtils.parseToCoin(amountInputTextField.getText(), bsqFormatter);
|
||||
try {
|
||||
Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(receiversAddressString, receiverAmount);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, true);
|
||||
|
@ -24,6 +24,7 @@ import bisq.desktop.components.AutoTooltipLabel;
|
||||
import bisq.desktop.components.AutoTooltipTableColumn;
|
||||
import bisq.desktop.components.HyperlinkWithIcon;
|
||||
import bisq.desktop.main.dao.wallet.BsqBalanceUtil;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
@ -367,7 +368,7 @@ public class BsqTxView extends ActivatableView<GridPane, Void> implements BsqBal
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
setText(bsqFormatter.formatDateTime(item.getDate()));
|
||||
setText(DisplayUtils.formatDateTime(item.getDate()));
|
||||
} else {
|
||||
setText("");
|
||||
}
|
||||
@ -630,7 +631,7 @@ public class BsqTxView extends ActivatableView<GridPane, Void> implements BsqBal
|
||||
style = "dao-tx-type-issuance-icon";
|
||||
int issuanceBlockHeight = daoFacade.getIssuanceBlockHeight(txId);
|
||||
long blockTime = daoFacade.getBlockTime(issuanceBlockHeight);
|
||||
String formattedDate = bsqFormatter.formatDateTime(new Date(blockTime));
|
||||
String formattedDate = DisplayUtils.formatDateTime(new Date(blockTime));
|
||||
toolTipText = Res.get("dao.tx.issuanceFromCompReq.tooltip", formattedDate);
|
||||
} else {
|
||||
awesomeIcon = AwesomeIcon.FILE_TEXT;
|
||||
@ -644,7 +645,7 @@ public class BsqTxView extends ActivatableView<GridPane, Void> implements BsqBal
|
||||
style = "dao-tx-type-issuance-icon";
|
||||
int issuanceBlockHeight = daoFacade.getIssuanceBlockHeight(txId);
|
||||
long blockTime = daoFacade.getBlockTime(issuanceBlockHeight);
|
||||
String formattedDate = bsqFormatter.formatDateTime(new Date(blockTime));
|
||||
String formattedDate = DisplayUtils.formatDateTime(new Date(blockTime));
|
||||
toolTipText = Res.get("dao.tx.issuanceFromReimbursement.tooltip", formattedDate);
|
||||
} else {
|
||||
awesomeIcon = AwesomeIcon.FILE_TEXT;
|
||||
|
@ -36,6 +36,7 @@ import bisq.core.locale.Res;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.DevEnv;
|
||||
@ -232,7 +233,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
||||
|
||||
walletService.addBalanceListener(balanceListener);
|
||||
amountTextFieldSubscription = EasyBind.subscribe(amountTextField.textProperty(), t -> {
|
||||
addressTextField.setAmountAsCoin(formatter.parseToCoin(t));
|
||||
addressTextField.setAmountAsCoin(ParsingUtils.parseToCoin(t, formatter));
|
||||
updateQRCode();
|
||||
});
|
||||
|
||||
@ -301,7 +302,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
||||
}
|
||||
|
||||
private Coin getAmountAsCoin() {
|
||||
return formatter.parseToCoin(amountTextField.getText());
|
||||
return ParsingUtils.parseToCoin(amountTextField.getText(), formatter);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -23,6 +23,7 @@ import bisq.desktop.components.AutoTooltipLabel;
|
||||
import bisq.desktop.components.HyperlinkWithIcon;
|
||||
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
|
||||
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.btc.listeners.BalanceListener;
|
||||
@ -228,7 +229,7 @@ public class LockedView extends ActivatableView<VBox, Void> {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
if (getTradable(item).isPresent())
|
||||
setGraphic(new AutoTooltipLabel(formatter.formatDateTime(getTradable(item).get().getDate())));
|
||||
setGraphic(new AutoTooltipLabel(DisplayUtils.formatDateTime(getTradable(item).get().getDate())));
|
||||
else
|
||||
setGraphic(new AutoTooltipLabel(Res.get("shared.noDateAvailable")));
|
||||
} else {
|
||||
|
@ -23,6 +23,7 @@ import bisq.desktop.components.AutoTooltipLabel;
|
||||
import bisq.desktop.components.HyperlinkWithIcon;
|
||||
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
|
||||
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.btc.listeners.BalanceListener;
|
||||
@ -228,7 +229,7 @@ public class ReservedView extends ActivatableView<VBox, Void> {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
if (getTradable(item).isPresent())
|
||||
setGraphic(new AutoTooltipLabel(formatter.formatDateTime(getTradable(item).get().getDate())));
|
||||
setGraphic(new AutoTooltipLabel(DisplayUtils.formatDateTime(getTradable(item).get().getDate())));
|
||||
else
|
||||
setGraphic(new AutoTooltipLabel(Res.get("shared.noDateAvailable")));
|
||||
} else {
|
||||
|
@ -18,6 +18,7 @@
|
||||
package bisq.desktop.main.funds.transactions;
|
||||
|
||||
import bisq.desktop.components.indicator.TxConfidenceIndicator;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.btc.listeners.TxConfidenceListener;
|
||||
@ -239,7 +240,7 @@ class TransactionsListItem {
|
||||
}
|
||||
// Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime()
|
||||
date = transaction.getIncludedInBestChainAt() != null ? transaction.getIncludedInBestChainAt() : transaction.getUpdateTime();
|
||||
dateString = formatter.formatDateTime(date);
|
||||
dateString = DisplayUtils.formatDateTime(date);
|
||||
|
||||
isDustAttackTx = received && valueSentToMe.value < ignoreDustThreshold;
|
||||
if (isDustAttackTx) {
|
||||
|
@ -41,6 +41,7 @@ import bisq.core.trade.TradeManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.CoinUtil;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.util.validation.BtcAddressValidator;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
@ -246,7 +247,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||
amountListener = (observable, oldValue, newValue) -> {
|
||||
if (amountTextField.focusedProperty().get()) {
|
||||
try {
|
||||
amountAsCoin = formatter.parseToCoin(amountTextField.getText());
|
||||
amountAsCoin = ParsingUtils.parseToCoin(amountTextField.getText(), formatter);
|
||||
} catch (Throwable t) {
|
||||
log.error("Error at amountTextField input. " + t.toString());
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import bisq.desktop.main.market.trades.TradesChartsView;
|
||||
import bisq.desktop.main.offer.offerbook.OfferBook;
|
||||
import bisq.desktop.main.offer.offerbook.OfferBookListItem;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
@ -183,11 +184,11 @@ public class MarketView extends ActivatableViewAndModel<TabPane, Activatable> {
|
||||
.map(trade -> {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Trade ID: ").append(trade.getOfferId()).append("\n")
|
||||
.append("Date: ").append(formatter.formatDateTime(trade.getTradeDate())).append("\n")
|
||||
.append("Market: ").append(formatter.getCurrencyPair(trade.getCurrencyCode())).append("\n")
|
||||
.append("Price: ").append(formatter.formatPrice(trade.getTradePrice())).append("\n")
|
||||
.append("Date: ").append(DisplayUtils.formatDateTime(trade.getTradeDate())).append("\n")
|
||||
.append("Market: ").append(BSFormatter.getCurrencyPair(trade.getCurrencyCode())).append("\n")
|
||||
.append("Price: ").append(BSFormatter.formatPrice(trade.getTradePrice())).append("\n")
|
||||
.append("Amount: ").append(formatter.formatCoin(trade.getTradeAmount())).append("\n")
|
||||
.append("Volume: ").append(formatter.formatVolume(trade.getTradeVolume())).append("\n")
|
||||
.append("Volume: ").append(DisplayUtils.formatVolume(trade.getTradeVolume())).append("\n")
|
||||
.append("Payment method: ").append(Res.get(trade.getOfferPaymentMethod())).append("\n")
|
||||
.append("ReferralID: ").append(trade.getExtraDataMap().get(OfferPayload.REFERRAL_ID));
|
||||
return sb.toString();
|
||||
@ -205,9 +206,9 @@ public class MarketView extends ActivatableViewAndModel<TabPane, Activatable> {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Offer ID: ").append(offer.getId()).append("\n")
|
||||
.append("Type: ").append(offer.getDirection().name()).append("\n")
|
||||
.append("Market: ").append(formatter.getCurrencyPair(offer.getCurrencyCode())).append("\n")
|
||||
.append("Price: ").append(formatter.formatPrice(offer.getPrice())).append("\n")
|
||||
.append("Amount: ").append(formatter.formatAmount(offer)).append(" BTC\n")
|
||||
.append("Market: ").append(BSFormatter.getCurrencyPair(offer.getCurrencyCode())).append("\n")
|
||||
.append("Price: ").append(BSFormatter.formatPrice(offer.getPrice())).append("\n")
|
||||
.append("Amount: ").append(DisplayUtils.formatAmount(offer, formatter)).append(" BTC\n")
|
||||
.append("Payment method: ").append(Res.get(offer.getPaymentMethod().getId())).append("\n")
|
||||
.append("ReferralID: ").append(offer.getOfferPayload().getExtraDataMap().get(OfferPayload.REFERRAL_ID));
|
||||
return sb.toString();
|
||||
|
@ -223,15 +223,15 @@ public class OfferBookChartView extends ActivatableViewAndModel<VBox, OfferBookC
|
||||
public String toString(Number object) {
|
||||
final double doubleValue = (double) object;
|
||||
if (CurrencyUtil.isCryptoCurrency(model.getCurrencyCode())) {
|
||||
final String withCryptoPrecision = formatter.formatRoundedDoubleWithPrecision(doubleValue, cryptoPrecision);
|
||||
final String withCryptoPrecision = BSFormatter.formatRoundedDoubleWithPrecision(doubleValue, cryptoPrecision);
|
||||
if (withCryptoPrecision.equals("0.000")) {
|
||||
cryptoPrecision = 8;
|
||||
return formatter.formatRoundedDoubleWithPrecision(doubleValue, cryptoPrecision);
|
||||
return BSFormatter.formatRoundedDoubleWithPrecision(doubleValue, cryptoPrecision);
|
||||
} else {
|
||||
return withCryptoPrecision;
|
||||
}
|
||||
} else {
|
||||
return formatter.formatRoundedDoubleWithPrecision(doubleValue, 2);
|
||||
return BSFormatter.formatRoundedDoubleWithPrecision(doubleValue, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,7 +268,7 @@ public class OfferBookChartView extends ActivatableViewAndModel<VBox, OfferBookC
|
||||
|
||||
priceColumnLabel.set(Res.get("shared.priceWithCur", code));
|
||||
}
|
||||
xAxis.setLabel(formatter.getPriceWithCurrencyCode(code));
|
||||
xAxis.setLabel(BSFormatter.getPriceWithCurrencyCode(code));
|
||||
|
||||
seriesBuy.setName(leftHeaderLabel.getText() + " ");
|
||||
seriesSell.setName(rightHeaderLabel.getText());
|
||||
|
@ -26,6 +26,7 @@ import bisq.desktop.main.settings.SettingsView;
|
||||
import bisq.desktop.main.settings.preferences.PreferencesView;
|
||||
import bisq.desktop.util.CurrencyList;
|
||||
import bisq.desktop.util.CurrencyListItem;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
@ -254,7 +255,7 @@ class OfferBookChartViewModel extends ActivatableViewModel {
|
||||
}
|
||||
|
||||
private String formatPrice(Offer offer, boolean decimalAligned) {
|
||||
return formatter.formatPrice(offer.getPrice(), decimalAligned, offer.isBuyOffer() ? maxPlacesForBuyPrice.get() : maxPlacesForSellPrice.get());
|
||||
return DisplayUtils.formatPrice(offer.getPrice(), decimalAligned, offer.isBuyOffer() ? maxPlacesForBuyPrice.get() : maxPlacesForSellPrice.get());
|
||||
}
|
||||
|
||||
public String getVolume(Offer offer) {
|
||||
@ -262,7 +263,7 @@ class OfferBookChartViewModel extends ActivatableViewModel {
|
||||
}
|
||||
|
||||
private String formatVolume(Offer offer, boolean decimalAligned) {
|
||||
return formatter.formatVolume(offer, decimalAligned, offer.isBuyOffer() ? maxPlacesForBuyVolume.get() : maxPlacesForSellVolume.get(), false);
|
||||
return DisplayUtils.formatVolume(offer, decimalAligned, offer.isBuyOffer() ? maxPlacesForBuyVolume.get() : maxPlacesForSellVolume.get(), false);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -185,7 +185,7 @@ class SpreadViewModel extends ActivatableViewModel {
|
||||
.multiply(BigDecimal.valueOf(10000))
|
||||
.divide(marketPriceAsBigDecimal, RoundingMode.HALF_UP)
|
||||
.doubleValue() / 10000;
|
||||
percentage = formatter.formatPercentagePrice(percentageValue);
|
||||
percentage = BSFormatter.formatPercentagePrice(percentageValue);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
try {
|
||||
|
@ -27,6 +27,7 @@ import bisq.desktop.components.ColoredDecimalPlacesWithZerosText;
|
||||
import bisq.desktop.main.market.trades.charts.price.CandleStickChart;
|
||||
import bisq.desktop.main.market.trades.charts.volume.VolumeChart;
|
||||
import bisq.desktop.util.CurrencyListItem;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
@ -239,7 +240,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
||||
String code = selectedTradeCurrency.getCode();
|
||||
volumeColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.amountWithCur", code)));
|
||||
|
||||
priceColumnLabel.set(formatter.getPriceWithCurrencyCode(code));
|
||||
priceColumnLabel.set(BSFormatter.getPriceWithCurrencyCode(code));
|
||||
|
||||
tableView.getColumns().remove(marketColumn);
|
||||
}
|
||||
@ -345,9 +346,9 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
||||
double doubleValue = (double) object;
|
||||
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
|
||||
final double value = MathUtils.scaleDownByPowerOf10(doubleValue, 8);
|
||||
return formatter.formatRoundedDoubleWithPrecision(value, 8);
|
||||
return BSFormatter.formatRoundedDoubleWithPrecision(value, 8);
|
||||
} else {
|
||||
return formatter.formatPrice(Price.valueOf(currencyCode, MathUtils.doubleToLong(doubleValue)));
|
||||
return BSFormatter.formatPrice(Price.valueOf(currencyCode, MathUtils.doubleToLong(doubleValue)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -362,9 +363,9 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
||||
public String toString(Number object) {
|
||||
if (CurrencyUtil.isCryptoCurrency(model.getCurrencyCode())) {
|
||||
final double value = MathUtils.scaleDownByPowerOf10((long) object, 8);
|
||||
return formatter.formatRoundedDoubleWithPrecision(value, 8);
|
||||
return BSFormatter.formatRoundedDoubleWithPrecision(value, 8);
|
||||
} else {
|
||||
return formatter.formatPrice(Price.valueOf(model.getCurrencyCode(), (long) object));
|
||||
return BSFormatter.formatPrice(Price.valueOf(model.getCurrencyCode(), (long) object));
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,9 +481,9 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
||||
long index = MathUtils.doubleToLong((double) object);
|
||||
long time = model.getTimeFromTickIndex(index);
|
||||
if (model.tickUnit.ordinal() <= TradesChartsViewModel.TickUnit.DAY.ordinal())
|
||||
return index % 4 == 0 ? formatter.formatDate(new Date(time)) : "";
|
||||
return index % 4 == 0 ? DisplayUtils.formatDate(new Date(time)) : "";
|
||||
else
|
||||
return index % 3 == 0 ? formatter.formatTime(new Date(time)) : "";
|
||||
return index % 3 == 0 ? DisplayUtils.formatTime(new Date(time)) : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -568,7 +569,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
||||
public void updateItem(final TradeStatistics2 item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(formatter.formatDateTime(item.getTradeDate()));
|
||||
setText(DisplayUtils.formatDateTime(item.getTradeDate()));
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
@ -597,7 +598,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
||||
public void updateItem(final TradeStatistics2 item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(formatter.getCurrencyPair(item.getCurrencyCode()));
|
||||
setText(BSFormatter.getCurrencyPair(item.getCurrencyCode()));
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
@ -621,7 +622,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
||||
public void updateItem(final TradeStatistics2 item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(formatter.formatPrice(item.getTradePrice()));
|
||||
setText(BSFormatter.formatPrice(item.getTradePrice()));
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
@ -671,8 +672,8 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(model.showAllTradeCurrenciesProperty.get() ?
|
||||
formatter.formatVolumeWithCode(item.getTradeVolume()) :
|
||||
formatter.formatVolume(item.getTradeVolume()));
|
||||
DisplayUtils.formatVolumeWithCode(item.getTradeVolume()) :
|
||||
DisplayUtils.formatVolume(item.getTradeVolume()));
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
@ -744,7 +745,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
||||
|
||||
@NotNull
|
||||
private String getDirectionLabel(TradeStatistics2 item) {
|
||||
return formatter.getDirectionWithCode(OfferPayload.Direction.valueOf(item.getDirection().name()), item.getCurrencyCode());
|
||||
return DisplayUtils.getDirectionWithCode(OfferPayload.Direction.valueOf(item.getDirection().name()), item.getCurrencyCode());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -25,6 +25,7 @@ import bisq.desktop.main.settings.SettingsView;
|
||||
import bisq.desktop.main.settings.preferences.PreferencesView;
|
||||
import bisq.desktop.util.CurrencyList;
|
||||
import bisq.desktop.util.CurrencyListItem;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.locale.CryptoCurrency;
|
||||
@ -345,8 +346,8 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
||||
final Date dateFrom = new Date(getTimeFromTickIndex(tick));
|
||||
final Date dateTo = new Date(getTimeFromTickIndex(tick + 1));
|
||||
String dateString = tickUnit.ordinal() > TickUnit.DAY.ordinal() ?
|
||||
formatter.formatDateTimeSpan(dateFrom, dateTo) :
|
||||
formatter.formatDate(dateFrom) + " - " + formatter.formatDate(dateTo);
|
||||
DisplayUtils.formatDateTimeSpan(dateFrom, dateTo) :
|
||||
DisplayUtils.formatDate(dateFrom) + " - " + DisplayUtils.formatDate(dateTo);
|
||||
return new CandleData(tick, open, close, high, low, averagePrice, medianPrice, accumulatedAmount, accumulatedVolume,
|
||||
numTrades, isBullish, dateString);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
package bisq.desktop.main.offer;
|
||||
|
||||
import bisq.desktop.Navigation;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeRestrictions;
|
||||
@ -55,6 +56,7 @@ import bisq.network.p2p.P2PService;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.util.MathUtils;
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
@ -589,6 +591,15 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
double calculateMarketPriceManual(double marketPrice, double volumeAsDouble, double amountAsDouble) {
|
||||
double manualPriceAsDouble = volumeAsDouble / amountAsDouble;
|
||||
double percentage = MathUtils.roundDouble(manualPriceAsDouble / marketPrice, 4);
|
||||
|
||||
setMarketPriceMargin(percentage);
|
||||
|
||||
return manualPriceAsDouble;
|
||||
}
|
||||
|
||||
void calculateVolume() {
|
||||
if (price.get() != null &&
|
||||
amount.get() != null &&
|
||||
@ -642,7 +653,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
||||
!price.get().isZero() &&
|
||||
allowAmountUpdate) {
|
||||
try {
|
||||
Coin value = btcFormatter.reduceTo4Decimals(price.get().getAmountByVolume(volume.get()));
|
||||
Coin value = DisplayUtils.reduceTo4Decimals(price.get().getAmountByVolume(volume.get()), btcFormatter);
|
||||
if (isHalCashAccount())
|
||||
value = OfferUtil.getAdjustedAmountForHalCash(value, price.get(), getMaxTradeLimit());
|
||||
else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get()))
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user