Move gui classes to gui module

This commit is contained in:
Manfred Karrer 2015-04-06 22:46:27 +02:00
parent 08dde43ffc
commit 7d4b30e8db
543 changed files with 438 additions and 257 deletions

View file

@ -0,0 +1,175 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.app;
import io.bitsquare.common.view.CachingViewLoader;
import io.bitsquare.common.view.View;
import io.bitsquare.common.view.ViewLoader;
import io.bitsquare.common.view.guice.InjectorViewFactory;
import io.bitsquare.gui.SystemTray;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.debug.DebugView;
import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.storage.FileManager;
import io.bitsquare.util.Utilities;
import com.google.common.base.Throwables;
import com.google.inject.Guice;
import com.google.inject.Injector;
import java.io.IOException;
import java.io.InvalidObjectException;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import static io.bitsquare.app.BitsquareEnvironment.APP_NAME_KEY;
public class BitsquareApp extends Application {
private static final Logger log = LoggerFactory.getLogger(BitsquareApp.class);
private static Environment env;
private BitsquareAppModule bitsquareAppModule;
private Injector injector;
private Stage primaryStage;
private Scene scene;
public static void setEnvironment(Environment env) {
BitsquareApp.env = env;
}
@Override
public void start(Stage primaryStage) throws IOException {
this.primaryStage = primaryStage;
log.trace("BitsquareApp.start");
try {
bitsquareAppModule = new BitsquareAppModule(env, primaryStage);
injector = Guice.createInjector(bitsquareAppModule);
injector.getInstance(InjectorViewFactory.class).setInjector(injector);
FileManager.setUncaughtExceptionHandler(
(t, throwable) -> Platform.runLater(
() -> Popups.handleUncaughtExceptions(Throwables.getRootCause(throwable))
)
);
Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) ->
Popups.handleUncaughtExceptions(Throwables.getRootCause(throwable)));
// load the main view and create the main scene
log.trace("viewLoader.load(MainView.class)");
CachingViewLoader viewLoader = injector.getInstance(CachingViewLoader.class);
View view = viewLoader.load(MainView.class);
scene = new Scene((Parent) view.getRoot(), 1000, 620);
scene.getStylesheets().setAll(
"/io/bitsquare/gui/bitsquare.css",
"/io/bitsquare/gui/images.css");
// configure the system tray
SystemTray.create(primaryStage, this::stop);
primaryStage.setOnCloseRequest(e -> {
e.consume();
stop();
});
scene.setOnKeyReleased(keyEvent -> {
// For now we exit when closing/quit the app.
// Later we will only hide the window (systemTray.hideStage()) and use the exit item in the system tray for
// shut down.
if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN).match(keyEvent) ||
new KeyCodeCombination(KeyCode.Q, KeyCombination.SHORTCUT_DOWN).match(keyEvent))
stop();
else if (new KeyCodeCombination(KeyCode.D, KeyCombination.SHORTCUT_DOWN).match(keyEvent))
showDebugWindow();
});
// configure the primary stage
primaryStage.setTitle(env.getRequiredProperty(APP_NAME_KEY));
primaryStage.setScene(scene);
primaryStage.setMinWidth(750);
primaryStage.setMinHeight(620);
// on windows the title icon is also used as task bar icon in a larger size
// on Linux no title icon is supported but also a large task bar icon is derived form that title icon
String iconPath;
if (Utilities.isOSX())
iconPath = ImageUtil.isRetina() ? "/images/window_icon@2x.png" : "/images/window_icon.png";
else if (Utilities.isWindows())
iconPath = "/images/task_bar_icon_windows.png";
else
iconPath = "/images/task_bar_icon_linux.png";
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream(iconPath)));
// make the UI visible
log.trace("primaryStage.show");
primaryStage.show();
//TODO just temp.
//showDebugWindow();
} catch (Throwable t) {
if (t instanceof InvalidObjectException) {
Popups.openErrorPopup("There is a problem with different version of persisted objects. " +
"Please delete the db directory inside the app directory.");
}
log.error(t.toString());
}
}
private void showDebugWindow() {
ViewLoader viewLoader = injector.getInstance(ViewLoader.class);
View debugView = viewLoader.load(DebugView.class);
Parent parent = (Parent) debugView.getRoot();
Stage stage = new Stage();
stage.setScene(new Scene(parent));
stage.setTitle("Debug window");
stage.initModality(Modality.NONE);
stage.initStyle(StageStyle.UTILITY);
stage.initOwner(scene.getWindow());
stage.setX(primaryStage.getX() + primaryStage.getWidth() + 10);
stage.setY(primaryStage.getY());
stage.show();
}
@Override
public void stop() {
bitsquareAppModule.close(injector);
System.exit(0);
}
}

View file

@ -0,0 +1,69 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.app;
import io.bitsquare.btc.UserAgent;
import io.bitsquare.btc.WalletService;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.p2p.tomp2p.TomP2PModule;
import io.bitsquare.storage.Storage;
import java.nio.file.Paths;
import java.util.Properties;
import joptsimple.OptionSet;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
public class BitsquareAppEnvironment extends BitsquareEnvironment {
public BitsquareAppEnvironment(OptionSet options) {
super(options);
}
BitsquareAppEnvironment(PropertySource commandLineProperties) {
super(commandLineProperties);
}
@Override
protected PropertySource<?> defaultProperties() {
return new PropertiesPropertySource(BITSQUARE_DEFAULT_PROPERTY_SOURCE_NAME, new Properties() {
private static final long serialVersionUID = -8478089705207326165L;
{
setProperty(APP_DATA_DIR_KEY, appDataDir);
setProperty(APP_DATA_DIR_CLEAN_KEY, DEFAULT_APP_DATA_DIR_CLEAN);
setProperty(APP_NAME_KEY, appName);
setProperty(UserAgent.NAME_KEY, appName);
setProperty(UserAgent.VERSION_KEY, BitsquareAppMain.getVersion());
setProperty(WalletService.DIR_KEY, appDataDir);
setProperty(WalletService.PREFIX_KEY, appName);
setProperty(Storage.DIR_KEY, Paths.get(appDataDir, "db").toString());
setProperty(MainView.TITLE_KEY, appName);
setProperty(TomP2PModule.BOOTSTRAP_NODE_PORT_KEY, bootstrapNodePort);
}
});
}
}

View file

@ -0,0 +1,157 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.app;
import io.bitsquare.BitsquareException;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.RegTestHost;
import io.bitsquare.p2p.BootstrapNodes;
import io.bitsquare.p2p.Node;
import io.bitsquare.util.joptsimple.EnumValueConverter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vinumeris.updatefx.UpdateFX;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import static io.bitsquare.app.BitsquareEnvironment.*;
import static io.bitsquare.p2p.Node.*;
import static io.bitsquare.p2p.tomp2p.TomP2PModule.*;
import static java.util.Arrays.asList;
public class BitsquareAppMain extends BitsquareExecutable {
private static final Logger log = LoggerFactory.getLogger(BitsquareAppMain.class);
private static final String VERSION = "0.1";
public static String getVersion() {
return VERSION + "." + UpdateProcess.getBuildVersion();
}
public static void main(String[] args) throws Exception {
// We don't want to do the full argument parsing here as that might easily change in update versions
// So we only handle the absolute minimum which is APP_NAME, APP_DATA_DIR_KEY and USER_DATA_DIR
OptionParser parser = new OptionParser();
parser.allowsUnrecognizedOptions();
parser.accepts(USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR))
.withRequiredArg();
parser.accepts(APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME))
.withRequiredArg();
OptionSet options;
try {
options = parser.parse(args);
} catch (OptionException ex) {
System.out.println("error: " + ex.getMessage());
System.out.println();
parser.printHelpOn(System.out);
System.exit(EXIT_FAILURE);
return;
}
BitsquareAppEnvironment bitsquareEnvironment = new BitsquareAppEnvironment(options);
String updatesDirectory = bitsquareEnvironment.getProperty(BitsquareEnvironment.APP_DATA_DIR_KEY);
// app dir need to be setup before UpdateFX bootstrap
initAppDir(updatesDirectory);
UpdateFX.bootstrap(BitsquareAppMain.class, new File(updatesDirectory).toPath(), args);
}
// That will be called from UpdateFX after updates are checked
public static void realMain(String[] args) throws Exception {
log.trace("realMain");
// For some reason the JavaFX launch process results in us losing the thread context class loader: reset it.
// In order to work around a bug in JavaFX 8u25 and below, you must include the following code as the first line of your realMain method:
Thread.currentThread().setContextClassLoader(BitsquareAppMain.class.getClassLoader());
new BitsquareAppMain().execute(args);
}
private static void initAppDir(String appDir) {
Path dir = Paths.get(appDir);
if (Files.exists(dir)) {
if (!Files.isWritable(dir))
throw new BitsquareException("Application data directory '%s' is not writeable", dir);
else
return;
}
try {
Files.createDirectory(dir);
} catch (IOException ex) {
throw new BitsquareException(ex, "Application data directory '%s' could not be created", dir);
}
}
@Override
protected void customizeOptionParsing(OptionParser parser) {
parser.accepts(USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR))
.withRequiredArg();
parser.accepts(APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME))
.withRequiredArg();
parser.accepts(APP_DATA_DIR_KEY, description("Application data directory", DEFAULT_APP_DATA_DIR))
.withRequiredArg();
parser.acceptsAll(asList(APP_DATA_DIR_CLEAN_KEY, "clean"),
description("Clean application data directory", DEFAULT_APP_DATA_DIR_CLEAN))
.withRequiredArg()
.ofType(boolean.class);
parser.accepts(NAME_KEY, description("Name of this node", null))
.withRequiredArg();
parser.accepts(PORT_KEY, description("Port to listen on", Node.DEFAULT_PORT))
.withRequiredArg()
.ofType(int.class);
parser.accepts(USE_MANUAL_PORT_FORWARDING_KEY, description("Use manual port forwarding", false))
.withRequiredArg()
.ofType(boolean.class);
parser.accepts(BitcoinNetwork.KEY, description("", BitcoinNetwork.DEFAULT))
.withRequiredArg()
.ofType(BitcoinNetwork.class)
.withValuesConvertedBy(new EnumValueConverter(BitcoinNetwork.class));
parser.accepts(RegTestHost.KEY, description("", RegTestHost.DEFAULT))
.withRequiredArg()
.ofType(RegTestHost.class)
.withValuesConvertedBy(new EnumValueConverter(RegTestHost.class));
parser.accepts(BOOTSTRAP_NODE_NAME_KEY, description("", BootstrapNodes.DEFAULT.getName()))
.withRequiredArg();
parser.accepts(BOOTSTRAP_NODE_IP_KEY, description("", BootstrapNodes.DEFAULT.getIp()))
.withRequiredArg();
parser.accepts(BOOTSTRAP_NODE_PORT_KEY, description("", BootstrapNodes.DEFAULT.getPort()))
.withRequiredArg()
.ofType(int.class);
parser.accepts(NETWORK_INTERFACE_KEY, description("Network interface", null))
.withRequiredArg();
}
@Override
protected void doExecute(OptionSet options) {
BitsquareApp.setEnvironment(new BitsquareAppEnvironment(options));
javafx.application.Application.launch(BitsquareApp.class);
}
}

View file

@ -0,0 +1,114 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.app;
import io.bitsquare.BitsquareModule;
import io.bitsquare.arbitration.ArbitratorModule;
import io.bitsquare.arbitration.tomp2p.TomP2PArbitratorModule;
import io.bitsquare.btc.BitcoinModule;
import io.bitsquare.crypto.CryptoModule;
import io.bitsquare.gui.GuiModule;
import io.bitsquare.offer.OfferModule;
import io.bitsquare.offer.tomp2p.TomP2POfferModule;
import io.bitsquare.p2p.P2PModule;
import io.bitsquare.p2p.tomp2p.TomP2PModule;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.TradeModule;
import io.bitsquare.user.AccountSettings;
import io.bitsquare.user.Preferences;
import io.bitsquare.user.User;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import java.io.File;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import static com.google.inject.name.Names.named;
class BitsquareAppModule extends BitsquareModule {
private static final Logger log = LoggerFactory.getLogger(BitsquareAppModule.class);
private final Stage primaryStage;
public BitsquareAppModule(Environment env, Stage primaryStage) {
super(env);
this.primaryStage = primaryStage;
}
@Override
protected void configure() {
bind(User.class).in(Singleton.class);
bind(Preferences.class).in(Singleton.class);
bind(AccountSettings.class).in(Singleton.class);
File storageDir = new File(env.getRequiredProperty(Storage.DIR_KEY));
bind(File.class).annotatedWith(named(Storage.DIR_KEY)).toInstance(storageDir);
bind(Environment.class).toInstance(env);
bind(UpdateProcess.class).in(Singleton.class);
// ordering is used for shut down sequence
install(tradeModule());
install(cryptoModule());
install(arbitratorModule());
install(offerModule());
install(p2pModule());
install(bitcoinModule());
install(guiModule());
}
protected TradeModule tradeModule() {
return new TradeModule(env);
}
protected CryptoModule cryptoModule() {
return new CryptoModule(env);
}
protected ArbitratorModule arbitratorModule() {
return new TomP2PArbitratorModule(env);
}
protected OfferModule offerModule() {
return new TomP2POfferModule(env);
}
protected P2PModule p2pModule() {
return new TomP2PModule(env);
}
protected BitcoinModule bitcoinModule() {
return new BitcoinModule(env);
}
protected GuiModule guiModule() {
return new GuiModule(env, primaryStage);
}
@Override
protected void doClose(Injector injector) {
log.trace("doClose " + getClass().getSimpleName());
}
}

View file

@ -0,0 +1,168 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.app;
import io.bitsquare.util.Utilities;
import com.google.inject.Inject;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
import javafx.animation.AnimationTimer;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vinumeris.updatefx.Crypto;
import com.vinumeris.updatefx.UpdateFX;
import com.vinumeris.updatefx.UpdateSummary;
import com.vinumeris.updatefx.Updater;
import org.bouncycastle.math.ec.ECPoint;
import org.springframework.core.env.Environment;
import rx.Observable;
import rx.subjects.BehaviorSubject;
import rx.subjects.Subject;
public class UpdateProcess {
private static final Logger log = LoggerFactory.getLogger(UpdateProcess.class);
// Edit version for updateFX
private static final int BUILD_VERSION = 3;
private static final List<ECPoint> UPDATE_SIGNING_KEYS = Crypto.decode("0296CFF54A8B1611499D4C1024E654140AFBB58C505FE4BB7C847B4F4A7C683DF6");
private static final String UPDATES_BASE_URL = "http://bitsquare.io/updateFX/";
private static final int UPDATE_SIGNING_THRESHOLD = 1;
private static final Path ROOT_CLASS_PATH = UpdateFX.findCodePath(BitsquareAppMain.class);
public static int getBuildVersion() {
return BUILD_VERSION;
}
private final Environment environment;
public enum State {
CHECK_FOR_UPDATES,
UPDATE_AVAILABLE,
UP_TO_DATE,
FAILURE
}
public final ObjectProperty<State> state = new SimpleObjectProperty<>(State.CHECK_FOR_UPDATES);
protected String errorMessage;
protected final Subject<State, State> process = BehaviorSubject.create();
protected AnimationTimer timeoutTimer;
@Inject
public UpdateProcess(Environment environment) {
this.environment = environment;
}
public void restart() {
UpdateFX.restartApp();
}
public Observable<State> getProcess() {
return process.asObservable();
}
public String getErrorMessage() {
return errorMessage;
}
public void init() {
log.info("UpdateFX current version " + BUILD_VERSION);
// process.timeout() will cause an error state back but we don't want to break startup in case of an timeout
timeoutTimer = Utilities.setTimeout(10000, animationTimer -> {
process.onCompleted();
return null;
});
timeoutTimer.start();
String agent = environment.getProperty(BitsquareEnvironment.APP_NAME_KEY) + BitsquareAppMain.getVersion();
Path dataDirPath = new File(environment.getProperty(BitsquareEnvironment.APP_DATA_DIR_KEY)).toPath();
Updater updater = new Updater(UPDATES_BASE_URL, agent, BUILD_VERSION, dataDirPath, ROOT_CLASS_PATH,
UPDATE_SIGNING_KEYS, UPDATE_SIGNING_THRESHOLD) {
@Override
protected void updateProgress(long workDone, long max) {
//log.trace("updateProgress " + workDone + "/" + max);
super.updateProgress(workDone, max);
}
};
updater.progressProperty().addListener((observableValue, oldValue, newValue) -> {
log.trace("progressProperty newValue = " + newValue);
});
log.info("Checking for updates!");
updater.setOnSucceeded(event -> {
try {
UpdateSummary summary = updater.get();
log.info("summary " + summary.toString());
if (summary.descriptions != null && summary.descriptions.size() > 0) {
log.info("One liner: {}", summary.descriptions.get(0).getOneLiner());
log.info("{}", summary.descriptions.get(0).getDescription());
}
if (summary.highestVersion > BUILD_VERSION) {
log.info("UPDATE_AVAILABLE");
state.set(State.UPDATE_AVAILABLE);
// We stop the timeout and treat it not completed.
// The user should click the restart button manually if there are updates available.
timeoutTimer.stop();
}
else if (summary.highestVersion == BUILD_VERSION) {
log.info("UP_TO_DATE");
state.set(State.UP_TO_DATE);
timeoutTimer.stop();
process.onCompleted();
}
} catch (Throwable e) {
log.error("Exception at processing UpdateSummary: " + e.getMessage());
// we treat errors as update not as critical errors to prevent startup,
// so we use state.onCompleted() instead of state.onError()
errorMessage = "Exception at processing UpdateSummary: " + e.getMessage();
state.set(State.FAILURE);
timeoutTimer.stop();
process.onCompleted();
}
});
updater.setOnFailed(event -> {
log.error("Update failed: " + updater.getException());
updater.getException().printStackTrace();
// we treat errors as update not as critical errors to prevent startup,
// so we use state.onCompleted() instead of state.onError()
errorMessage = "Update failed: " + updater.getException();
state.set(State.FAILURE);
timeoutTimer.stop();
process.onCompleted();
});
Thread thread = new Thread(updater, "Online update check");
thread.setDaemon(true);
thread.start();
}
}

View file

@ -0,0 +1,33 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common;
import static java.lang.String.format;
public class ViewfxException extends RuntimeException {
private static final long serialVersionUID = -6266047448442595372L;
public ViewfxException(Throwable cause, String format, Object... args) {
super(format(format, args), cause);
}
public ViewfxException(String format, Object... args) {
super(format(format, args));
}
}

View file

@ -0,0 +1,115 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.fxml;
import io.bitsquare.common.ViewfxException;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.View;
import io.bitsquare.common.view.ViewFactory;
import io.bitsquare.common.view.ViewLoader;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javax.inject.Inject;
import javafx.fxml.FXMLLoader;
import org.springframework.core.annotation.AnnotationUtils;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.springframework.core.annotation.AnnotationUtils.getDefaultValue;
public class FxmlViewLoader implements ViewLoader {
private final ViewFactory viewFactory;
private final ResourceBundle resourceBundle;
@Inject
public FxmlViewLoader(ViewFactory viewFactory, ResourceBundle resourceBundle) {
this.viewFactory = viewFactory;
this.resourceBundle = resourceBundle;
}
@SuppressWarnings("unchecked")
public View load(Class<? extends View> viewClass) {
FxmlView fxmlView = AnnotationUtils.getAnnotation(viewClass, FxmlView.class);
final Class<? extends FxmlView.PathConvention> convention;
final Class<? extends FxmlView.PathConvention> defaultConvention =
(Class<? extends FxmlView.PathConvention>) getDefaultValue(FxmlView.class, "convention");
final String specifiedLocation;
final String defaultLocation = (String) getDefaultValue(FxmlView.class, "location");
if (fxmlView == null) {
convention = defaultConvention;
specifiedLocation = defaultLocation;
}
else {
convention = fxmlView.convention();
specifiedLocation = fxmlView.location();
}
if (convention == null || specifiedLocation == null)
throw new IllegalStateException("Convention and location should never be null.");
try {
final String resolvedLocation;
if (specifiedLocation.equals(defaultLocation))
resolvedLocation = convention.newInstance().apply(viewClass);
else
resolvedLocation = specifiedLocation;
URL fxmlUrl = viewClass.getClassLoader().getResource(resolvedLocation);
if (fxmlUrl == null)
throw new ViewfxException(
"Failed to load view class [%s] because FXML file at [%s] could not be loaded " +
"as a classpath resource. Does it exist?", viewClass, specifiedLocation);
return loadFromFxml(fxmlUrl);
} catch (InstantiationException | IllegalAccessException ex) {
throw new ViewfxException(ex, "Failed to load view from class %s", viewClass);
}
}
public View loadFromFxml(URL fxmlUrl) {
checkNotNull(fxmlUrl, "FXML URL must not be null");
try {
FXMLLoader loader = new FXMLLoader(fxmlUrl, resourceBundle);
loader.setControllerFactory(viewFactory);
loader.load();
Object controller = loader.getController();
if (controller == null)
throw new ViewfxException("Failed to load view from FXML file at [%s]. " +
"Does it declare an fx:controller attribute?", fxmlUrl);
if (!(controller instanceof View))
throw new ViewfxException("Controller of type [%s] loaded from FXML file at [%s] " +
"does not implement [%s] as expected.", controller.getClass(), fxmlUrl, View.class);
return (View) controller;
} catch (IOException ex) {
throw new ViewfxException(ex, "Failed to load view from FXML file at [%s]", fxmlUrl);
}
}
}

View file

@ -0,0 +1,37 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.model;
public interface Activatable {
void activate();
void deactivate();
Activatable NOOP_INSTANCE = new Activatable() {
@Override
public void activate() {
}
@Override
public void deactivate() {
}
};
}

View file

@ -0,0 +1,43 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.model;
public abstract class ActivatableWithDataModel<D extends Activatable> extends WithDataModel<D> implements Activatable {
public ActivatableWithDataModel(D dataModel) {
super(dataModel);
}
@Override
public final void activate() {
dataModel.activate();
this.doActivate();
}
protected void doActivate() {
}
@Override
public final void deactivate() {
dataModel.deactivate();
this.doDeactivate();
}
protected void doDeactivate() {
}
}

View file

@ -0,0 +1,21 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.model;
public interface DataModel extends Model {
}

View file

@ -0,0 +1,21 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.model;
public interface Model {
}

View file

@ -0,0 +1,21 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.model;
public interface ViewModel extends Model {
}

View file

@ -0,0 +1,29 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.model;
import static com.google.common.base.Preconditions.checkNotNull;
public abstract class WithDataModel<D> {
protected final D dataModel;
protected WithDataModel(D dataModel) {
this.dataModel = checkNotNull(dataModel, "Delegate object must not be null");
}
}

View file

@ -0,0 +1,44 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view;
import javafx.fxml.FXML;
import javafx.scene.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractView<R extends Node, M> implements View {
protected final Logger log = LoggerFactory.getLogger(this.getClass());
protected @FXML R root;
protected final M model;
public AbstractView(M model) {
this.model = model;
}
public AbstractView() {
this(null);
}
public R getRoot() {
return root;
}
}

View file

@ -0,0 +1,49 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view;
import javafx.scene.*;
public abstract class ActivatableView<R extends Node, M> extends InitializableView<R, M> {
public ActivatableView(M model) {
super(model);
}
public ActivatableView() {
this(null);
}
@Override
protected void prepareInitialize() {
if (root != null) {
root.sceneProperty().addListener((ov, oldValue, newValue) -> {
if (oldValue == null && newValue != null)
activate();
else if (oldValue != null && newValue == null)
deactivate();
});
}
}
protected void activate() {
}
protected void deactivate() {
}
}

View file

@ -0,0 +1,53 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view;
import io.bitsquare.common.model.Activatable;
import javafx.scene.*;
import static com.google.common.base.Preconditions.checkNotNull;
public abstract class ActivatableViewAndModel<R extends Node, M extends Activatable> extends ActivatableView<R, M> {
public ActivatableViewAndModel(M model) {
super(checkNotNull(model, "Model must not be null"));
}
public ActivatableViewAndModel() {
this((M) Activatable.NOOP_INSTANCE);
}
@Override
public final void activate() {
model.activate();
this.doActivate();
}
protected void doActivate() {
}
@Override
public final void deactivate() {
model.deactivate();
this.doDeactivate();
}
protected void doDeactivate() {
}
}

View file

@ -0,0 +1,43 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view;
import java.util.HashMap;
import javax.inject.Inject;
public class CachingViewLoader implements ViewLoader {
private final HashMap<Object, View> cache = new HashMap<>();
private final ViewLoader viewLoader;
@Inject
public CachingViewLoader(ViewLoader viewLoader) {
this.viewLoader = viewLoader;
}
@Override
public View load(Class<? extends View> viewClass) {
if (cache.containsKey(viewClass))
return cache.get(viewClass);
View view = viewLoader.load(viewClass);
cache.put(viewClass, view);
return view;
}
}

View file

@ -0,0 +1,27 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view;
import org.springframework.util.ClassUtils;
public class DefaultPathConvention implements FxmlView.PathConvention {
@Override
public String apply(Class<? extends View> viewClass) {
return ClassUtils.convertClassNameToResourcePath(viewClass.getName()).concat(".fxml");
}
}

View file

@ -0,0 +1,46 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view;
import java.util.function.Function;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FxmlView {
/**
* The location of the FXML file associated with annotated {@link View} class. By default the location will be
* determined by {@link #convention()}.
*/
String location() default "";
/**
* The function used to determine the location of the FXML file associated with the annotated {@link View} class.
* By default it is the fully-qualified view class name, converted to a resource path, replacing the
* {@code .class} suffix replaced with {@code .fxml}.
*/
Class<? extends PathConvention> convention() default DefaultPathConvention.class;
interface PathConvention extends Function<Class<? extends View>, String> {
}
}

View file

@ -0,0 +1,48 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.Initializable;
import javafx.scene.*;
public abstract class InitializableView<R extends Node, M> extends AbstractView<R, M> implements Initializable {
public InitializableView(M model) {
super(model);
}
public InitializableView() {
this(null);
}
@Override
public final void initialize(URL location, ResourceBundle resources) {
prepareInitialize();
initialize();
}
protected void prepareInitialize() {
}
protected void initialize() {
}
}

View file

@ -0,0 +1,24 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view;
import javafx.scene.*;
public interface View {
Node getRoot();
}

View file

@ -0,0 +1,23 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view;
import javafx.util.Callback;
public interface ViewFactory extends Callback<Class<?>, Object> {
}

View file

@ -0,0 +1,22 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view;
public interface ViewLoader {
View load(Class<? extends View> viewClass);
}

View file

@ -0,0 +1,57 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class ViewPath extends ArrayList<Class<? extends View>> implements Serializable {
// That object is saved to disc. We need to take care of changes to not break deserialization.
private static final long serialVersionUID = 1L;
public ViewPath() {
}
public ViewPath(Collection<? extends Class<? extends View>> c) {
super(c);
}
public static ViewPath to(Class<? extends View>... elements) {
ViewPath path = new ViewPath();
List<Class<? extends View>> list = Arrays.asList(elements);
path.addAll(list);
return path;
}
public static ViewPath from(ViewPath original) {
ViewPath path = new ViewPath();
path.addAll(original);
return path;
}
public Class<? extends View> tip() {
if (size() == 0)
return null;
return get(size() - 1);
}
}

View file

@ -0,0 +1,28 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view;
public interface Wizard extends View {
void nextStep(Step currentStep);
interface Step {
void hideWizardNavigation();
void setWizard(Wizard wizard);
}
}

View file

@ -0,0 +1,39 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.view.guice;
import io.bitsquare.common.view.ViewFactory;
import com.google.common.base.Preconditions;
import com.google.inject.Injector;
public class InjectorViewFactory implements ViewFactory {
private Injector injector;
public void setInjector(Injector injector) {
this.injector = injector;
}
@Override
public Object call(Class<?> aClass) {
Preconditions.checkNotNull(injector, "Injector has not yet been provided");
return injector.getInstance(aClass);
}
}

View file

@ -0,0 +1,83 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui;
import io.bitsquare.BitsquareModule;
import io.bitsquare.common.fxml.FxmlViewLoader;
import io.bitsquare.common.view.CachingViewLoader;
import io.bitsquare.common.view.ViewFactory;
import io.bitsquare.common.view.ViewLoader;
import io.bitsquare.common.view.guice.InjectorViewFactory;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.offer.offerbook.OfferBook;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.Transitions;
import io.bitsquare.gui.util.validation.BankAccountNumberValidator;
import io.bitsquare.gui.util.validation.BtcValidator;
import io.bitsquare.gui.util.validation.FiatValidator;
import io.bitsquare.gui.util.validation.InputValidator;
import io.bitsquare.gui.util.validation.PasswordValidator;
import io.bitsquare.locale.BSResources;
import com.google.inject.Singleton;
import com.google.inject.name.Names;
import java.util.ResourceBundle;
import javafx.stage.Stage;
import org.springframework.core.env.Environment;
public class GuiModule extends BitsquareModule {
private final Stage primaryStage;
public GuiModule(Environment env, Stage primaryStage) {
super(env);
this.primaryStage = primaryStage;
}
@Override
protected void configure() {
bind(InjectorViewFactory.class).in(Singleton.class);
bind(ViewFactory.class).to(InjectorViewFactory.class);
bind(ResourceBundle.class).toInstance(BSResources.getResourceBundle());
bind(ViewLoader.class).to(FxmlViewLoader.class).in(Singleton.class);
bind(CachingViewLoader.class).in(Singleton.class);
bind(Navigation.class).in(Singleton.class);
bind(OfferBook.class).in(Singleton.class);
bind(OverlayManager.class).in(Singleton.class);
bind(BSFormatter.class).in(Singleton.class);
bind(BankAccountNumberValidator.class).in(Singleton.class);
bind(BtcValidator.class).in(Singleton.class);
bind(FiatValidator.class).in(Singleton.class);
bind(InputValidator.class).in(Singleton.class);
bind(PasswordValidator.class).in(Singleton.class);
bind(Transitions.class).in(Singleton.class);
bind(Stage.class).toInstance(primaryStage);
Popups.primaryStage = primaryStage;
bindConstant().annotatedWith(Names.named(MainView.TITLE_KEY)).to(env.getRequiredProperty(MainView.TITLE_KEY));
}
}

View file

@ -0,0 +1,142 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui;
import io.bitsquare.common.view.View;
import io.bitsquare.common.view.ViewPath;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.offer.BuyOfferView;
import io.bitsquare.storage.Storage;
import com.google.inject.Inject;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Navigation implements Serializable {
// That object is saved to disc. We need to take care of changes to not break deserialization.
private static final long serialVersionUID = 1L;
transient private static final Logger log = LoggerFactory.getLogger(Navigation.class);
transient private static final ViewPath DEFAULT_VIEW_PATH = ViewPath.to(MainView.class, BuyOfferView.class);
public interface Listener {
void onNavigationRequested(ViewPath path);
}
// New listeners can be added during iteration so we use CopyOnWriteArrayList to
// prevent invalid array modification
transient private final List<Listener> listeners = new CopyOnWriteArrayList<>();
transient private final Storage<Navigation> storage;
transient private ViewPath currentPath;
// Used for returning to the last important view. After setup is done we want to
// return to the last opened view (e.g. sell/buy)
transient private ViewPath returnPath;
// Persisted fields
private ViewPath previousPath;
@Inject
public Navigation(Storage<Navigation> storage) {
this.storage = storage;
Navigation persisted = storage.initAndGetPersisted(this);
if (persisted != null) {
previousPath = persisted.getPreviousPath();
}
else
previousPath = DEFAULT_VIEW_PATH;
// need to be null initially and not DEFAULT_VIEW_PATH to navigate through all items
currentPath = null;
}
@SuppressWarnings("unchecked")
public void navigateTo(Class<? extends View>... viewClasses) {
navigateTo(ViewPath.to(viewClasses));
}
public void navigateTo(ViewPath newPath) {
if (newPath == null)
return;
ArrayList<Class<? extends View>> temp = new ArrayList<>();
for (int i = 0; i < newPath.size(); i++) {
Class<? extends View> viewClass = newPath.get(i);
temp.add(viewClass);
if (currentPath == null ||
(currentPath != null &&
currentPath.size() > i &&
viewClass != currentPath.get(i) &&
i != newPath.size() - 1)) {
ArrayList<Class<? extends View>> temp2 = new ArrayList<>(temp);
for (int n = i + 1; n < newPath.size(); n++) {
Class<? extends View>[] newTemp = new Class[i + 1];
currentPath = ViewPath.to(temp2.toArray(newTemp));
navigateTo(currentPath);
viewClass = newPath.get(n);
temp2.add(viewClass);
}
}
}
currentPath = newPath;
previousPath = currentPath;
storage.queueUpForSave();
listeners.stream().forEach((e) -> e.onNavigationRequested(currentPath));
}
public void navigateToPreviousVisitedView() {
if (previousPath == null || previousPath.size() == 0)
previousPath = DEFAULT_VIEW_PATH;
navigateTo(previousPath);
}
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
public ViewPath getReturnPath() {
return returnPath;
}
public ViewPath getCurrentPath() {
return currentPath;
}
public void setReturnPath(ViewPath returnPath) {
this.returnPath = returnPath;
}
private ViewPath getPreviousPath() {
return previousPath;
}
}

View file

@ -0,0 +1,59 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.List;
public class OverlayManager {
private final List<OverlayListener> listeners = new ArrayList<>();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public OverlayManager() {
}
public void blurContent() {
listeners.stream().forEach(OverlayListener::onBlurContentRequested);
}
public void removeBlurContent() {
listeners.stream().forEach(OverlayListener::onRemoveBlurContentRequested);
}
public void addListener(OverlayListener listener) {
listeners.add(listener);
}
public void removeListener(OverlayListener listener) {
listeners.remove(listener);
}
public interface OverlayListener {
void onBlurContentRequested();
void onRemoveBlurContentRequested();
}
}

View file

@ -0,0 +1,135 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui;
import io.bitsquare.BitsquareException;
import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.util.Utilities;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javafx.application.Platform;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
/**
* There is no JavaFX support yet, so we need to use AWT.
*/
public class SystemTray {
private static final Logger log = LoggerFactory.getLogger(SystemTray.class);
private static final String ICON_HI_RES = "/images/system_tray_icon@2x.png";
private static final String ICON_LO_RES = "/images/system_tray_icon.png";
private static final String ICON_WINDOWS = "/images/system_tray_icon_windows.png";
private static final String ICON_LINUX = "/images/system_tray_icon_linux.png";
private static final String SHOW_WINDOW_LABEL = "Show exchange window";
private static final String HIDE_WINDOW_LABEL = "Hide exchange window";
private static SystemTray systemTray;
private final Stage stage;
private final Runnable onExit;
private final MenuItem toggleShowHideItem = new MenuItem(HIDE_WINDOW_LABEL);
public static void create(Stage stage, Runnable onExit) {
systemTray = new SystemTray(stage, onExit);
}
public SystemTray(Stage stage, Runnable onExit) {
this.stage = stage;
this.onExit = onExit;
init();
}
private void init() {
if (!java.awt.SystemTray.isSupported()) {
log.error("System tray is not supported.");
return;
}
// prevent exiting the app when the last window gets closed
// For now we allow to close the app by closing the window.
// Later we only let it close via the system trays exit.
Platform.setImplicitExit(true);
MenuItem aboutItem = new MenuItem("Info about Bitsquare");
MenuItem exitItem = new MenuItem("Exit");
PopupMenu popupMenu = new PopupMenu();
popupMenu.add(aboutItem);
popupMenu.addSeparator();
popupMenu.add(toggleShowHideItem);
popupMenu.addSeparator();
popupMenu.add(exitItem);
String path;
if (Utilities.isOSX())
path = ImageUtil.isRetina() ? ICON_HI_RES : ICON_LO_RES;
else if (Utilities.isWindows())
path = ICON_WINDOWS;
else
path = ICON_LINUX;
try {
BufferedImage trayIconImage = ImageIO.read(getClass().getResource(path));
TrayIcon trayIcon = new TrayIcon(trayIconImage);
// On Windows and Linux the icon needs to be scaled
// On OSX we get the correct size from the provided image
if (!Utilities.isOSX()) {
int trayIconWidth = trayIcon.getSize().width;
trayIcon = new TrayIcon(trayIconImage.getScaledInstance(trayIconWidth, -1, Image.SCALE_SMOOTH));
}
trayIcon.setPopupMenu(popupMenu);
trayIcon.setToolTip("Bitsquare: The decentralized bitcoin exchange");
java.awt.SystemTray.getSystemTray().add(trayIcon);
} catch (AWTException e1) {
log.error("Icon could not be added to system tray.", e1);
} catch (IOException e2) {
throw new BitsquareException(e2);
}
toggleShowHideItem.addActionListener(e -> {
if (stage.isShowing()) {
toggleShowHideItem.setLabel(SHOW_WINDOW_LABEL);
Platform.runLater(stage::hide);
}
else {
toggleShowHideItem.setLabel(HIDE_WINDOW_LABEL);
Platform.runLater(stage::show);
}
});
exitItem.addActionListener(e -> onExit.run());
}
public void hideStage() {
stage.hide();
toggleShowHideItem.setLabel(SHOW_WINDOW_LABEL);
}
}

View file

@ -0,0 +1,555 @@
/*
Theme colors:
logo colors:
new blue: 0f87c3
new grey: 666666
00abff
orange webpage quotes : ff7f00
main bg grey: dddddd
content bg grey: f4f4f4
tab pane upper bg gradient color mid dark grey to bright grey: cfcfcf -> dddddd
upper border on tab: cfcfcf
lower border on tab: b5b5b5
upper gradient color on tab: d3d3d3
lower gradient color on tab: dddddd
*/
.root {
-bs-grey: #666666;
-bs-light-grey: #cccccc;
-bs-bg-grey: #dddddd;
-bs-bg-green: #00aa33;
-bs-error-red: #dd0000;
-fx-accent: #0f87c3;
-fx-default-button: derive(-fx-accent, 95%);
-fx-focus-color: -fx-accent;
-fx-faint-focus-color: #0f87c322;
-fx-selection-bar: derive(-fx-accent, 50%);
}
/* Splash */
#splash {
-fx-background-color: #ffffff;
}
#splash-error-state-msg {
-fx-text-fill: -bs-error-red;
}
#splash-bitcoin-network-label {
-fx-text-fill: -fx-accent;
-fx-weight: bold;
}
/* Main UI */
#base-content-container {
-fx-background-color: -bs-bg-grey;
}
#content-pane {
-fx-background-color: #f4f4f4;
}
#headline-label {
-fx-font-weight: bold;
-fx-font-size: 18;
}
/* Main navigation */
#nav-button {
-fx-cursor: hand;
-fx-background-color: transparent;
}
#nav-button .text {
-fx-font-size: 10;
}
#nav-button:selected .text {
-fx-font-size: 11;
-fx-font-weight: bold;
-fx-fill: -fx-accent;
}
#nav-button-label {
-fx-font-size: 10;
}
#nav-balance-label {
-fx-font-weight: bold;
-fx-alignment: center;
-fx-background-color: #dddddd;
}
#nav-alert-label {
-fx-font-weight: bold;
-fx-alignment: center;
-fx-font-size: 11;
-fx-text-fill: white;
}
.text-field:readonly {
-fx-text-fill: #000000;
-fx-background-color: #FAFAFA;
}
#feedback-text {
-fx-font-size: 10;
}
#label-url {
-fx-cursor: hand;
-fx-text-fill: blue;
-fx-underline: true;
}
#icon-button {
-fx-cursor: hand;
-fx-background-color: transparent;
}
.copy-icon {
-fx-text-fill: -fx-accent;
-fx-cursor: hand;
}
.copy-icon:hover {
-fx-text-fill: black;
}
/*******************************************************************************
* *
* Tooltip *
* *
******************************************************************************/
.tooltip {
-fx-background: white;
-fx-text-fill: black;
-fx-background-color: white;
-fx-background-radius: 6px;
-fx-background-insets: 0;
-fx-padding: 0.667em 0.75em 0.667em 0.75em; /* 10px */
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.5), 10, 0.0, 0, 3);
-fx-font-size: 0.85em;
}
/* Same style like non editable textfield. But textfield spans a whole column in a grid, so we use generally
textfield */
#label-with-background {
-fx-background-color: #FAFAFA;
-fx-border-radius: 4;
-fx-padding: 4 4 4 4;
}
#address-text-field {
-fx-cursor: hand;
-fx-text-fill: -fx-accent;
}
#address-text-field:hover {
-fx-text-fill: black;
}
#funds-confidence {
-fx-progress-color: dimgrey;
}
/*******************************************************************************
* *
* Table *
* *
******************************************************************************/
.table-view .table-cell {
-fx-alignment: center;
}
.table-view .column-header .label {
-fx-alignment: center;
}
.table-view .focus {
-fx-alignment: center;
}
.table-view .text {
-fx-fill: black;
}
.table-view:focused {
/*-fx-background-color: transparent;*/
}
/* horizontal scrollbars are never needed and are flickering at scaling so lets turn them off */
.table-view > .scroll-bar:horizontal {
-fx-opacity: 0;
}
.table-view:focused {
-fx-background-color: -fx-box-border, -fx-control-inner-background;
-fx-background-insets: 0, 1;
-fx-padding: 1;
}
.table-view .table-row-cell:selected .table-row-cell:row-selection .table-row-cell:cell-selection .text {
-fx-fill: white;
}
.table-view .table-row-cell:selected .button .text {
-fx-fill: black;
}
.table-view .table-row-cell .copy-icon .text {
-fx-fill: -fx-accent;
}
.table-view .table-row-cell .copy-icon .text:hover {
-fx-fill: black;
}
.table-view .table-row-cell:selected .copy-icon .text {
-fx-fill: white;
}
.table-view .table-row-cell:selected .copy-icon .text:hover {
-fx-fill: black;
}
.table-view .table-row-cell .hyperlink .text {
-fx-fill: -fx-accent;
}
.table-view .table-row-cell .hyperlink .text:hover {
-fx-fill: black;
}
.table-view .table-row-cell:selected .hyperlink .text {
-fx-fill: white;
}
.table-view .table-row-cell:selected .hyperlink .text:hover {
-fx-fill: black;
}
/*******************************************************************************
* *
* Icons *
* *
******************************************************************************/
#non-clickable-icon {
-fx-text-fill: #AAAAAA;
}
#clickable-icon {
-fx-text-fill: -fx-accent;
-fx-cursor: hand;
}
#clickable-icon:hover {
-fx-text-fill: #666666;
}
/*******************************************************************************
* *
* Tab pane *
* *
******************************************************************************/
.tab-pane .tab-label {
-fx-font-size: 15;
}
.tab-pane:focused {
-fx-background-color: transparent;
}
.tab-header-area:focused {
-fx-background-color: transparent;
}
.tab:focused {
-fx-background-color: transparent;
}
#form-header-text {
-fx-font-weight: bold;
-fx-font-size: 14;
}
#form-title {
-fx-font-weight: bold;
}
/* scroll-pane */
.scroll-pane {
-fx-background-insets: 0;
-fx-padding: 0;
}
.scroll-pane:focused {
-fx-background-insets: 0;
}
.scroll-pane .corner {
-fx-background-insets: 0;
}
/* validation */
#validation-error {
-fx-text-fill: red;
}
/* Account */
#content-pane-top {
-fx-background-color: #cfcfcf,
linear-gradient(#cfcfcf 0%, #b5b5b5 100%),
linear-gradient(#d3d3d3 0%, -bs-bg-grey 100%);
-fx-background-insets: 0 0 0 0, 0, 1;
}
#info-icon-label {
-fx-font-size: 16;
-fx-text-fill: #333000;
}
/* Create offer */
#direction-icon-label {
-fx-font-weight: bold;
-fx-font-size: 16;
-fx-text-fill: -bs-grey;
}
#input-description-label {
-fx-font-size: 11;
-fx-alignment: center;
}
#create-offer-calc-label {
-fx-font-weight: bold;
-fx-font-size: 20;
-fx-padding: 15 5 0 5;
}
#currency-info-label {
-fx-border-radius: 0 4 4 0;
-fx-padding: 4 4 4 4;
-fx-background-color: #f6f6f6;
-fx-border-color: #aaa;
-fx-border-style: solid solid solid none;
-fx-border-insets: 0 0 0 -2;
}
#totals-separator {
-fx-background: #AAAAAA;
}
#payment-info {
-fx-background-color: #f4f4f4;
}
/* Account setup */
#wizard-title-deactivated {
-fx-font-weight: bold;
-fx-font-size: 16;
-fx-text-fill: #999999;
}
#wizard-title-active {
-fx-font-weight: bold;
-fx-font-size: 16;
-fx-text-fill: #333333;
}
#wizard-title-completed {
-fx-font-weight: bold;
-fx-font-size: 16;
-fx-text-fill: #333333;
}
#wizard-sub-title-deactivated {
-fx-text-fill: #999999;
}
#wizard-sub-title-active {
-fx-text-fill: #333333;
}
#wizard-sub-title-completed {
-fx-text-fill: #333333;
}
#wizard-item-background-deactivated {
-fx-body-color: linear-gradient(to bottom, #f4f4f4, #F0F0F0);
-fx-outer-border: linear-gradient(to bottom, #dddddd, #ccc);
-fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border,
-fx-inner-border,
-fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px;
}
#wizard-item-background-active {
-fx-body-color: linear-gradient(to bottom, #f1f6f7, #e7f5f9);
-fx-outer-border: linear-gradient(to bottom, #b5e1ef, #6aa4b6);
-fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border,
-fx-inner-border,
-fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px;
}
#wizard-item-background-completed {
-fx-body-color: linear-gradient(to bottom, #f4f4f4, #F0F0F0);
-fx-outer-border: linear-gradient(to bottom, #99ba9c, #619865);
-fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border,
-fx-inner-border,
-fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px;
}
/* Account settings */
#wizard-title-disabled {
-fx-font-weight: bold;
-fx-font-size: 16;
-fx-text-fill: #999999;
}
#wizard-title-active {
-fx-font-weight: bold;
-fx-font-size: 16;
-fx-text-fill: #333333;
}
#wizard-title-selected {
-fx-font-weight: bold;
-fx-font-size: 16;
-fx-text-fill: -fx-accent;
}
#account-settings-item-background-disabled {
-fx-body-color: linear-gradient(to bottom, #f4f4f4, #F0F0F0);
-fx-outer-border: linear-gradient(to bottom, #dddddd, #ccc);
-fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border,
-fx-inner-border,
-fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px;
}
#account-settings-item-background-active {
-fx-body-color: linear-gradient(to bottom, #f4f4f4, #F0F0F0);
-fx-outer-border: linear-gradient(to bottom, #dddddd, #ccc);
-fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border,
-fx-inner-border,
-fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px;
}
#account-settings-item-background-selected {
-fx-body-color: linear-gradient(to bottom, #f1f6f7, #e7f5f9);
-fx-outer-border: linear-gradient(to bottom, #b5e1ef, #6aa4b6);
-fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border,
-fx-inner-border,
-fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px;
}
/* Pending trades */
#trade-wizard-item-background-disabled {
-fx-text-fill: -bs-grey;
-fx-body-color: linear-gradient(to bottom, #f4f4f4, #F0F0F0);
-fx-outer-border: linear-gradient(to bottom, #dddddd, #ccc);
-fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border,
-fx-inner-border,
-fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px;
}
#trade-wizard-item-background-active {
-fx-font-weight: bold;
-fx-font-size: 14;
-fx-body-color: linear-gradient(to bottom, #f1f6f7, #e7f5f9);
-fx-outer-border: linear-gradient(to bottom, #b5e1ef, #6aa4b6);
-fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border,
-fx-inner-border,
-fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px;
}
#trade-wizard-item-background-completed {
-fx-body-color: linear-gradient(to bottom, #f4f4f4, #E1E9E1);
-fx-outer-border: linear-gradient(to bottom, #99ba9c, #619865);
-fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border,
-fx-inner-border,
-fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px;
}
/* TitledGroupBg */
#titled-group-bg-label {
-fx-font-weight: bold;
-fx-font-size: 14;
-fx-text-fill: -bs-grey;
-fx-background-color: #f4f4f4;
}
#titled-group-bg-label-active {
-fx-font-weight: bold;
-fx-font-size: 14;
-fx-text-fill: -fx-accent;
-fx-background-color: #f4f4f4;
}
#titled-group-bg {
-fx-body-color: linear-gradient(to bottom, #f4f4f4, #F0F0F0);
-fx-outer-border: linear-gradient(to bottom, #dddddd, #ccc);
-fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border,
-fx-inner-border,
-fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px;
}
#titled-group-bg-active {
-fx-body-color: linear-gradient(to bottom, #f4f4f4, #F0F0F0);
-fx-outer-border: linear-gradient(to bottom, #9bbdc9, #57acc9);
-fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border,
-fx-inner-border,
-fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px;
}
/* TitledSeparator */
#titled-separator:horizontal .line {
-fx-border-color: transparent #f4f4f4 transparent #f4f4f4;
-fx-background-color: transparent;
-fx-border-width: 10px;
}

View file

@ -0,0 +1,192 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import io.bitsquare.gui.OverlayManager;
import io.bitsquare.util.Utilities;
import org.bitcoinj.core.Coin;
import org.bitcoinj.uri.BitcoinURI;
import java.io.ByteArrayInputStream;
import java.net.URI;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.stage.Window;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import net.glxn.qrgen.QRCode;
import net.glxn.qrgen.image.ImageType;
import org.controlsfx.control.PopOver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AddressTextField extends AnchorPane {
private static final Logger log = LoggerFactory.getLogger(AddressTextField.class);
private final StringProperty address = new SimpleStringProperty();
private final StringProperty paymentLabel = new SimpleStringProperty();
private final ObjectProperty<Coin> amountAsCoin = new SimpleObjectProperty<>();
private OverlayManager overlayManager;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public AddressTextField() {
TextField textField = new TextField();
textField.setId("address-text-field");
textField.setEditable(false);
textField.textProperty().bind(address);
Tooltip.install(textField, new Tooltip("Open your default Bitcoin wallet with that address."));
textField.setOnMouseClicked(mouseEvent -> {
try {
Utilities.openURI(URI.create(getBitcoinURI()));
} catch (Exception e) {
log.warn(e.getMessage());
Popups.openWarningPopup("Warning", "Opening a system Bitcoin wallet application has failed. " +
"Perhaps you don't have one installed?");
}
});
textField.focusTraversableProperty().set(focusTraversableProperty().get());
focusedProperty().addListener((ov, oldValue, newValue) -> textField.requestFocus());
Label copyIcon = new Label();
copyIcon.setLayoutY(3);
copyIcon.getStyleClass().add("copy-icon");
Tooltip.install(copyIcon, new Tooltip("Copy address to clipboard"));
AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY);
copyIcon.setOnMouseClicked(e -> {
if (address.get() != null && address.get().length() > 0)
Utilities.copyToClipboard(address.get());
});
Label qrCode = new Label();
qrCode.getStyleClass().add("copy-icon");
qrCode.setLayoutY(3);
AwesomeDude.setIcon(qrCode, AwesomeIcon.QRCODE);
Tooltip.install(qrCode, new Tooltip("Show QR code for this address"));
qrCode.setOnMouseClicked(e -> {
if (address.get() != null && address.get().length() > 0) {
final byte[] imageBytes = QRCode
.from(getBitcoinURI())
.withSize(300, 220)
.to(ImageType.PNG)
.stream()
.toByteArray();
Image qrImage = new Image(new ByteArrayInputStream(imageBytes));
ImageView view = new ImageView(qrImage);
Pane pane = new Pane(view);
pane.setPrefSize(320, 240);
view.relocate(10, 10);
PopOver popOver = new PopOver(pane);
popOver.setDetachedTitle("Scan QR code for this address");
popOver.setDetached(true);
popOver.setOnHiding(windowEvent -> {
if (overlayManager != null)
overlayManager.removeBlurContent();
});
Window window = getScene().getWindow();
double x = Math.round(window.getX() + (window.getWidth() - 320) / 2);
double y = Math.round(window.getY() + (window.getHeight() - 240) / 2);
popOver.show(getScene().getWindow(), x, y);
if (overlayManager != null)
overlayManager.blurContent();
}
});
AnchorPane.setRightAnchor(qrCode, 5.0);
AnchorPane.setRightAnchor(copyIcon, 30.0);
AnchorPane.setRightAnchor(textField, 55.0);
AnchorPane.setLeftAnchor(textField, 0.0);
getChildren().addAll(textField, copyIcon, qrCode);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters/Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setAddress(String address) {
this.address.set(address);
}
public String getAddress() {
return address.get();
}
public StringProperty addressProperty() {
return address;
}
public Coin getAmountAsCoin() {
return amountAsCoin.get();
}
public ObjectProperty<Coin> amountAsCoinProperty() {
return amountAsCoin;
}
public void setAmountAsCoin(Coin amountAsCoin) {
this.amountAsCoin.set(amountAsCoin);
}
public String getPaymentLabel() {
return paymentLabel.get();
}
public StringProperty paymentLabelProperty() {
return paymentLabel;
}
public void setPaymentLabel(String paymentLabel) {
this.paymentLabel.set(paymentLabel);
}
// TODO find better solution without OverlayManager dependency
public void setOverlayManager(OverlayManager overlayManager) {
this.overlayManager = overlayManager;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private String getBitcoinURI() {
return address.get() != null ? BitcoinURI.convertToBitcoinURI(address.get(), amountAsCoin.get(),
paymentLabel.get(), null) : "";
}
}

View file

@ -0,0 +1,81 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.gui.util.BSFormatter;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import javafx.scene.control.*;
import javafx.scene.effect.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
public class BalanceTextField extends AnchorPane {
private final TextField textField;
private final Effect fundedEffect = new DropShadow(BlurType.THREE_PASS_BOX, Color.GREEN, 4, 0.0, 0, 0);
private final Effect notFundedEffect = new DropShadow(BlurType.THREE_PASS_BOX, Color.ORANGERED, 4, 0.0, 0, 0);
private BSFormatter formatter;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public BalanceTextField() {
textField = new TextField();
textField.setFocusTraversable(false);
textField.setEditable(false);
AnchorPane.setRightAnchor(textField, 0.0);
AnchorPane.setLeftAnchor(textField, 0.0);
getChildren().addAll(textField);
}
public void setup(WalletService walletService, Address address, BSFormatter formatter) {
this.formatter = formatter;
walletService.addBalanceListener(new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balance) {
updateBalance(balance);
}
});
updateBalance(walletService.getBalanceForAddress(address));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
private void updateBalance(Coin balance) {
textField.setText(formatter.formatCoinWithCode(balance));
if (balance.isPositive())
textField.setEffect(fundedEffect);
else
textField.setEffect(notFundedEffect);
}
}

View file

@ -0,0 +1,135 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.AddressConfidenceListener;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
import io.bitsquare.gui.util.BSFormatter;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.TransactionConfidence;
import javafx.scene.control.*;
import javafx.scene.effect.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
public class BalanceWithConfirmationTextField extends AnchorPane {
private final TextField textField;
private final Tooltip progressIndicatorTooltip;
private final ConfidenceProgressIndicator progressIndicator;
private final Effect fundedEffect = new DropShadow(BlurType.THREE_PASS_BOX, Color.GREEN, 4, 0.0, 0, 0);
private final Effect notFundedEffect = new DropShadow(BlurType.THREE_PASS_BOX, Color.ORANGERED, 4, 0.0, 0, 0);
private BSFormatter formatter;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public BalanceWithConfirmationTextField() {
textField = new TextField();
textField.setFocusTraversable(false);
textField.setEditable(false);
progressIndicator = new ConfidenceProgressIndicator();
progressIndicator.setFocusTraversable(false);
progressIndicator.setPrefSize(24, 24);
progressIndicator.setId("funds-confidence");
progressIndicator.setLayoutY(1);
progressIndicator.setProgress(0);
progressIndicator.setVisible(false);
progressIndicatorTooltip = new Tooltip("-");
Tooltip.install(progressIndicator, progressIndicatorTooltip);
AnchorPane.setRightAnchor(progressIndicator, 0.0);
AnchorPane.setRightAnchor(textField, 55.0);
AnchorPane.setLeftAnchor(textField, 0.0);
getChildren().addAll(textField, progressIndicator);
}
public void setup(WalletService walletService, Address address, BSFormatter formatter) {
this.formatter = formatter;
walletService.addAddressConfidenceListener(new AddressConfidenceListener(address) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
updateConfidence(confidence);
}
});
updateConfidence(walletService.getConfidenceForAddress(address));
walletService.addBalanceListener(new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balance) {
updateBalance(balance);
}
});
updateBalance(walletService.getBalanceForAddress(address));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
private void updateConfidence(TransactionConfidence confidence) {
if (confidence != null) {
switch (confidence.getConfidenceType()) {
case UNKNOWN:
progressIndicatorTooltip.setText("Unknown transaction status");
progressIndicator.setProgress(0);
break;
case PENDING:
progressIndicatorTooltip.setText(
"Seen by " + confidence.numBroadcastPeers() + " peer(s) / 0 " + "confirmations");
progressIndicator.setProgress(-1.0);
break;
case BUILDING:
progressIndicatorTooltip.setText("Confirmed in " + confidence.getDepthInBlocks() + " block(s)");
progressIndicator.setProgress(Math.min(1, (double) confidence.getDepthInBlocks() / 6.0));
break;
case DEAD:
progressIndicatorTooltip.setText("Transaction is invalid.");
progressIndicator.setProgress(0);
break;
}
if (progressIndicator.getProgress() != 0) {
progressIndicator.setVisible(true);
AnchorPane.setRightAnchor(progressIndicator, 0.0);
AnchorPane.setRightAnchor(textField, 35.0);
}
}
}
private void updateBalance(Coin balance) {
textField.setText(formatter.formatCoinWithCode(balance));
if (balance.isPositive())
textField.setEffect(fundedEffect);
else
textField.setEffect(notFundedEffect);
}
}

View file

@ -0,0 +1,238 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import io.bitsquare.locale.BSResources;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.*;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
/**
* Convenience Component for info icon, info text and link display in a GridPane.
* Only the properties needed are supported.
* We need to extend from Parent so we can use it in FXML, but the InfoDisplay is not used as node,
* but add the children nodes to the gridPane.
*/
public class InfoDisplay extends Parent {
private final StringProperty text = new SimpleStringProperty();
private final IntegerProperty rowIndex = new SimpleIntegerProperty(0);
private final IntegerProperty columnIndex = new SimpleIntegerProperty(0);
private final ObjectProperty<EventHandler<ActionEvent>> onAction = new SimpleObjectProperty<>();
private final ObjectProperty<GridPane> gridPane = new SimpleObjectProperty<>();
private boolean useReadMore;
private final Label icon = AwesomeDude.createIconLabel(AwesomeIcon.INFO_SIGN);
private final TextFlow textFlow;
private final Label label;
private final Hyperlink link;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public InfoDisplay() {
icon.setId("non-clickable-icon");
icon.visibleProperty().bind(visibleProperty());
GridPane.setValignment(icon, VPos.TOP);
GridPane.setMargin(icon, new Insets(-2, 0, 0, 0));
GridPane.setRowSpan(icon, 2);
label = new Label();
label.textProperty().bind(text);
label.setTextOverrun(OverrunStyle.WORD_ELLIPSIS);
// width is set a frame later so we hide it first
label.setVisible(false);
link = new Hyperlink(BSResources.get("shared.readMore"));
link.setPadding(new Insets(0, 0, 0, -2));
// We need that to know if we have a wrapping or not.
// Did not find a way to get that from the API.
Label testLabel = new Label();
testLabel.textProperty().bind(text);
textFlow = new TextFlow();
textFlow.visibleProperty().bind(visibleProperty());
textFlow.getChildren().addAll(testLabel);
testLabel.widthProperty().addListener((ov, o, n) -> {
useReadMore = (double) n > textFlow.getWidth();
link.setText(BSResources.get(useReadMore ? "shared.readMore" : "shared.openHelp"));
Platform.runLater(() -> textFlow.getChildren().setAll(label, link));
});
// update the width when the window gets resized
ChangeListener<Number> listener = (ov2, oldValue2, windowWidth) -> {
if (label.prefWidthProperty().isBound())
label.prefWidthProperty().unbind();
label.setPrefWidth((double) windowWidth - localToScene(0, 0).getX() - 35);
};
// when clicking "Read more..." we expand and change the link to the Help
link.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent actionEvent) {
if (useReadMore) {
label.setWrapText(true);
link.setText(BSResources.get("shared.openHelp"));
getScene().getWindow().widthProperty().removeListener(listener);
if (label.prefWidthProperty().isBound())
label.prefWidthProperty().unbind();
label.prefWidthProperty().bind(textFlow.widthProperty());
link.setVisited(false);
// focus border is a bit confusing here so we remove it
link.setStyle("-fx-focus-color: transparent;");
link.setOnAction(onAction.get());
getParent().layout();
}
else {
onAction.get().handle(actionEvent);
}
}
});
sceneProperty().addListener((ov, oldValue, newValue) -> {
if (oldValue == null && newValue != null && newValue.getWindow() != null) {
newValue.getWindow().widthProperty().addListener(listener);
// localToScene does deliver 0 instead of the correct x position when scene propery gets set,
// so we delay for 1 render cycle
Platform.runLater(() -> {
label.setVisible(true);
label.prefWidthProperty().unbind();
label.setPrefWidth(newValue.getWindow().getWidth() - localToScene(0, 0).getX() - 35);
});
}
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setText(String text) {
this.text.set(text);
Platform.runLater(() -> {
if (getScene() != null) {
label.setVisible(true);
label.prefWidthProperty().unbind();
label.setPrefWidth(getScene().getWindow().getWidth() - localToScene(0, 0).getX() - 35);
}
});
}
public void setGridPane(GridPane gridPane) {
this.gridPane.set(gridPane);
gridPane.getChildren().addAll(icon, textFlow);
GridPane.setColumnIndex(icon, columnIndex.get());
GridPane.setColumnIndex(textFlow, columnIndex.get() + 1);
GridPane.setRowIndex(icon, rowIndex.get());
GridPane.setRowIndex(textFlow, rowIndex.get());
}
public void setRowIndex(int rowIndex) {
this.rowIndex.set(rowIndex);
GridPane.setRowIndex(icon, rowIndex);
GridPane.setRowIndex(textFlow, rowIndex);
}
public void setColumnIndex(int columnIndex) {
this.columnIndex.set(columnIndex);
GridPane.setColumnIndex(icon, columnIndex);
GridPane.setColumnIndex(textFlow, columnIndex + 1);
}
public final void setOnAction(EventHandler<ActionEvent> eventHandler) {
onAction.set(eventHandler);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public String getText() {
return text.get();
}
public StringProperty textProperty() {
return text;
}
public int getColumnIndex() {
return columnIndex.get();
}
public IntegerProperty columnIndexProperty() {
return columnIndex;
}
public int getRowIndex() {
return rowIndex.get();
}
public IntegerProperty rowIndexProperty() {
return rowIndex;
}
public EventHandler<ActionEvent> getOnAction() {
return onAction.get();
}
public ObjectProperty<EventHandler<ActionEvent>> onActionProperty() {
return onAction;
}
public GridPane getGridPane() {
return gridPane.get();
}
public ObjectProperty<GridPane> gridPaneProperty() {
return gridPane;
}
}

View file

@ -0,0 +1,176 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import io.bitsquare.gui.util.validation.InputValidator;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.control.*;
import javafx.scene.effect.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.stage.Window;
import org.controlsfx.control.PopOver;
/**
* TextField with validation support.
* If validator is set it supports on focus out validation with that validator. If a more sophisticated validation is
* needed the validationResultProperty can be used for applying validation result done by external validation.
* In case the isValid property in validationResultProperty get set to false we display a red border and an error
* message within the errorMessageDisplay placed on the right of the text field.
* The errorMessageDisplay gets closed when the ValidatingTextField instance gets removed from the scene graph or when
* hideErrorMessageDisplay() is called.
* 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.
public class InputTextField extends TextField {
private final Effect invalidEffect = new DropShadow(BlurType.THREE_PASS_BOX, Color.RED, 4, 0.0, 0, 0);
private final ObjectProperty<InputValidator.ValidationResult> validationResult = new SimpleObjectProperty<>
(new InputValidator.ValidationResult(true));
private static PopOver errorMessageDisplay;
private Region layoutReference = this;
public InputValidator getValidator() {
return validator;
}
public void setValidator(InputValidator validator) {
this.validator = validator;
}
private InputValidator validator;
///////////////////////////////////////////////////////////////////////////////////////////
// Static
///////////////////////////////////////////////////////////////////////////////////////////
public static void hideErrorMessageDisplay() {
if (errorMessageDisplay != null)
errorMessageDisplay.hide();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public InputTextField() {
super();
validationResult.addListener((ov, oldValue, newValue) -> {
if (newValue != null) {
setEffect(newValue.isValid ? null : invalidEffect);
if (newValue.isValid)
hideErrorMessageDisplay();
else
applyErrorMessage(newValue);
}
});
sceneProperty().addListener((ov, oldValue, newValue) -> {
// we got removed from the scene so hide the popup (if open)
if (newValue == null)
hideErrorMessageDisplay();
});
focusedProperty().addListener((o, oldValue, newValue) -> {
if (oldValue && !newValue && validator != null)
validationResult.set(validator.validate(getText()));
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public methods
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
/**
* @param layoutReference The node used as reference for positioning. If not set explicitly the
* ValidatingTextField instance is used.
*/
public void setLayoutReference(Region layoutReference) {
this.layoutReference = layoutReference;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public ObjectProperty<InputValidator.ValidationResult> validationResultProperty() {
return validationResult;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
private void applyErrorMessage(InputValidator.ValidationResult validationResult) {
if (errorMessageDisplay != null)
errorMessageDisplay.hide();
if (!validationResult.isValid) {
createErrorPopOver(validationResult.errorMessage);
if (getScene() != null)
errorMessageDisplay.show(getScene().getWindow(), getErrorPopupPosition().getX(),
getErrorPopupPosition().getY());
if (errorMessageDisplay != null)
errorMessageDisplay.setDetached(false);
}
}
private Point2D getErrorPopupPosition() {
Window window = getScene().getWindow();
Point2D point;
point = layoutReference.localToScene(0, 0);
double x = Math.floor(point.getX() + window.getX() + layoutReference.getWidth() + 20 - getPadding().getLeft() -
getPadding().getRight());
double y = Math.floor(point.getY() + window.getY() + getHeight() / 2 - getPadding().getTop() - getPadding()
.getBottom());
return new Point2D(x, y);
}
private static void createErrorPopOver(String errorMessage) {
Label errorLabel = new Label(errorMessage);
errorLabel.setId("validation-error");
errorLabel.setPadding(new Insets(0, 10, 0, 10));
errorLabel.setOnMouseClicked(e -> hideErrorMessageDisplay());
errorMessageDisplay = new PopOver(errorLabel);
errorMessageDisplay.setDetachable(true);
errorMessageDisplay.setDetachedTitle("Close");
errorMessageDisplay.setArrowIndent(5);
}
}

View file

@ -0,0 +1,72 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import javafx.animation.FadeTransition;
import javafx.animation.Interpolator;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.util.Duration;
// TODO replace with new notification component from lighthouse/bitcoinJ
public class NetworkSyncPane extends HBox {
private final ProgressBar networkSyncProgressBar;
private final Label networkSyncInfoLabel;
public NetworkSyncPane() {
networkSyncInfoLabel = new Label();
networkSyncInfoLabel.setText("Synchronize with network...");
networkSyncProgressBar = new ProgressBar();
networkSyncProgressBar.setPrefWidth(200);
networkSyncProgressBar.setProgress(-1);
getChildren().addAll(new HSpacer(5), networkSyncProgressBar, networkSyncInfoLabel);
}
public void setProgress(double percent) {
networkSyncProgressBar.setProgress(percent / 100.0);
networkSyncInfoLabel.setText("Synchronize with network: " + (int) percent + "%");
}
public void downloadComplete() {
networkSyncInfoLabel.setText("Sync with network: Done");
networkSyncProgressBar.setProgress(1);
FadeTransition fade = new FadeTransition(Duration.millis(700), this);
fade.setToValue(0.0);
fade.setCycleCount(1);
fade.setInterpolator(Interpolator.EASE_BOTH);
fade.play();
fade.setOnFinished(e -> getChildren().clear());
}
}
class HSpacer extends Pane {
public HSpacer(double width) {
setPrefWidth(width);
}
@Override
protected double computePrefWidth(double width) {
return getPrefWidth();
}
}

View file

@ -0,0 +1,258 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import io.bitsquare.gui.OverlayManager;
import io.bitsquare.locale.BSResources;
import org.bitcoinj.store.BlockStoreException;
import com.google.common.base.Throwables;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.stage.Stage;
import org.controlsfx.control.action.AbstractAction;
import org.controlsfx.control.action.Action;
import org.controlsfx.dialog.Dialog;
import org.controlsfx.dialog.Dialogs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Popups {
private static final Logger log = LoggerFactory.getLogger(Popups.class);
public static Stage primaryStage;
// TODO just temporary, class will be removed completely
public static void setOverlayManager(OverlayManager overlayManager) {
Popups.overlayManager = overlayManager;
}
private static OverlayManager overlayManager;
public static void removeBlurContent() {
overlayManager.removeBlurContent();
}
// Information
public static void openInfoPopup(String message) {
openInfoPopup(null, message);
}
public static void openInfoPopup(String masthead, String message) {
overlayManager.blurContent();
List<Action> actions = new ArrayList<>();
actions.add(new AbstractAction(BSResources.get("shared.close")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "CLOSE");
Dialog.Actions.CLOSE.handle(actionEvent);
}
});
openInfoPopup(masthead, message, actions);
}
public static void openInfoPopup(String masthead, String message, List<Action> actions) {
Dialogs.create()
.owner(primaryStage)
.message(message)
.masthead(masthead)
.actions(actions)
.showInformation();
removeBlurContent();
}
// Confirm
public static Action openConfirmPopup(String title, String message) {
return openConfirmPopup(title, null, message);
}
public static Action openConfirmPopup(String title, String masthead, String message) {
overlayManager.blurContent();
List<Action> actions = new ArrayList<>();
actions.add(new AbstractAction(BSResources.get("shared.ok")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "OK");
Dialog.Actions.OK.handle(actionEvent);
}
});
actions.add(new AbstractAction(BSResources.get("shared.cancel")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "CANCEL");
Dialog.Actions.CANCEL.handle(actionEvent);
}
});
return openConfirmPopup(title, masthead, message, actions);
}
public static Action openConfirmPopup(String title, String masthead, String message, List<Action> actions) {
return Dialogs.create()
.owner(primaryStage)
.title(title)
.message(message)
.masthead(masthead)
.actions(actions)
.showConfirm();
}
// Warning
public static void openWarningPopup(String message) {
openWarningPopup("Warning", message);
}
public static void openWarningPopup(String title, String message) {
openWarningPopup(title, null, message);
}
public static void openWarningPopup(String title, String masthead, String message) {
overlayManager.blurContent();
List<Action> actions = new ArrayList<>();
actions.add(new AbstractAction(BSResources.get("shared.close")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "CLOSE");
Dialog.Actions.CLOSE.handle(actionEvent);
}
});
openWarningPopup(title, masthead, message, actions);
}
private static void openWarningPopup(String title, String masthead, String message, List<Action> actions) {
Dialogs.create()
.owner(primaryStage)
.title(title)
.message(message)
.masthead(masthead)
.actions(actions)
.showWarning();
removeBlurContent();
}
// Error
public static void openErrorPopup(String message) {
openErrorPopup("Error", message);
}
public static void openErrorPopup(String title, String message) {
openErrorPopup(title, null, message);
}
public static void openErrorPopup(String title, String masthead, String message) {
overlayManager.blurContent();
List<Action> actions = new ArrayList<>();
actions.add(new AbstractAction(BSResources.get("shared.close")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "CLOSE");
Dialog.Actions.CLOSE.handle(actionEvent);
}
});
openErrorPopup(title, masthead, message, actions);
}
private static void openErrorPopup(String title, String masthead, String message, List<Action> actions) {
Dialogs.create()
.owner(primaryStage)
.title(title)
.message(message)
.masthead(masthead)
.actions(actions)
.showError();
removeBlurContent();
}
// Exception
public static void openExceptionPopup(Throwable throwable) {
openExceptionPopup(throwable, "Exception", "That should not have happened...");
}
public static void openExceptionPopup(Throwable throwable, String title, String message) {
openExceptionPopup(throwable, title, null, message);
}
private static void openExceptionPopup(Throwable throwable, String title, String masthead, String message) {
overlayManager.blurContent();
List<Action> actions = new ArrayList<>();
actions.add(new AbstractAction(BSResources.get("shared.close")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "CLOSE");
Dialog.Actions.CLOSE.handle(actionEvent);
}
});
Dialogs.create()
.owner(primaryStage)
.title(title)
.message(message)
.masthead(masthead)
.actions(actions)
.showException(throwable);
removeBlurContent();
}
// Support handling of uncaught exception from any thread (also non gui thread)
public static void handleUncaughtExceptions(Throwable throwable) {
// while dev
log.error(throwable.getMessage());
log.error(throwable.toString());
throwable.printStackTrace();
Runnable runnable = () -> {
if (Throwables.getRootCause(throwable) instanceof BlockStoreException) {
Popups.openErrorPopup("Error", "Application already running",
"This application is already running and cannot be started twice.\n\n " +
"Check your system tray to reopen the window of the running application.");
Platform.exit();
}
else {
Popups.openExceptionPopup(throwable, "Exception", "A critical error has occurred.",
"Please copy the exception details and open a bug report at:\n " +
"https://github.com/bitsquare/bitsquare/issues.");
Platform.exit();
}
};
if (Platform.isFxApplicationThread())
runnable.run();
else
Platform.runLater(runnable);
}
// custom
public static void openInsufficientMoneyPopup() {
openWarningPopup("Warning", "There is not enough money available",
"Please pay in first to your wallet.");
}
public static boolean isOK(Action response) {
return response.getProperties().get("type").equals("OK");
}
public static boolean isYes(Action response) {
return response.getProperties().get("type").equals("YES");
}
}

View file

@ -0,0 +1,39 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import io.bitsquare.util.Utilities;
import eu.hansolo.enzo.notification.Notification;
import eu.hansolo.enzo.notification.NotificationBuilder;
import eu.hansolo.enzo.notification.NotifierBuilder;
/**
* Not sure if we stick with the eu.hansolo.enzo.notification.Notification implementation, so keep it behind a service
*/
public class SystemNotification {
private static final Notification.Notifier notifier = NotifierBuilder.create().build();
public static void openInfoNotification(String title, String message) {
// On windows it causes problems with the hidden stage used in the hansolo Notification implementation
// Lets deactivate it for the moment and fix that with a more native-like or real native solution later.
// Lets deactivate it for Linux as well, as it is not much tested yet
if (Utilities.isOSX())
notifier.notify(NotificationBuilder.create().title(title).message(message).build());
}
}

View file

@ -0,0 +1,78 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import io.bitsquare.util.Utilities;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
public class TextFieldWithCopyIcon extends AnchorPane {
private final StringProperty text = new SimpleStringProperty();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public TextFieldWithCopyIcon() {
Label copyIcon = new Label();
copyIcon.setLayoutY(3);
copyIcon.getStyleClass().add("copy-icon");
Tooltip.install(copyIcon, new Tooltip("Copy to clipboard"));
AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY);
AnchorPane.setRightAnchor(copyIcon, 0.0);
copyIcon.setOnMouseClicked(e -> {
if (getText() != null && getText().length() > 0)
Utilities.copyToClipboard(getText());
});
TextField txIdLabel = new TextField();
txIdLabel.setEditable(false);
txIdLabel.textProperty().bindBidirectional(text);
AnchorPane.setRightAnchor(txIdLabel, 30.0);
AnchorPane.setLeftAnchor(txIdLabel, 0.0);
txIdLabel.focusTraversableProperty().set(focusTraversableProperty().get());
focusedProperty().addListener((ov, oldValue, newValue) -> txIdLabel.requestFocus());
getChildren().addAll(txIdLabel, copyIcon);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getter/Setter
///////////////////////////////////////////////////////////////////////////////////////////
public String getText() {
return text.get();
}
public StringProperty textProperty() {
return text;
}
public void setText(String text) {
this.text.set(text);
}
}

View file

@ -0,0 +1,71 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.scene.control.*;
import javafx.scene.layout.*;
public class TitledGroupBg extends Pane {
private final Label label;
private final StringProperty text = new SimpleStringProperty();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public TitledGroupBg() {
GridPane.setMargin(this, new Insets(-10, -10, -10, -10));
GridPane.setColumnSpan(this, 2);
label = new Label();
label.textProperty().bind(text);
label.setLayoutX(8);
label.setLayoutY(-8);
label.setPadding(new Insets(0, 7, 0, 5));
setActive();
getChildren().add(label);
}
public void setInactive() {
setId("titled-group-bg");
label.setId("titled-group-bg-label");
}
public void setActive() {
setId("titled-group-bg-active");
label.setId("titled-group-bg-label-active");
}
public String getText() {
return text.get();
}
public StringProperty textProperty() {
return text;
}
public void setText(String text) {
this.text.set(text);
}
}

View file

@ -0,0 +1,78 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.scene.control.*;
import javafx.scene.layout.*;
public class TitledSeparator extends Pane {
private final Label label;
private final StringProperty text = new SimpleStringProperty();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public TitledSeparator() {
GridPane.setMargin(this, new Insets(-10, -10, -10, -10));
GridPane.setColumnSpan(this, 2);
Separator separator = new Separator();
separator.prefWidthProperty().bind(widthProperty());
separator.setLayoutX(0);
separator.setLayoutY(6);
separator.setId("titled-separator");
label = new Label();
label.textProperty().bind(text);
label.setLayoutX(8);
label.setLayoutY(-8);
label.setPadding(new Insets(0, 7, 0, 5));
setActive();
getChildren().addAll(separator, label);
}
public void setInactive() {
setId("titled-group-bg");
label.setId("titled-group-bg-label");
}
public void setActive() {
setId("titled-group-bg-active");
label.setId("titled-group-bg-label-active");
}
public String getText() {
return text.get();
}
public StringProperty textProperty() {
return text;
}
public void setText(String text) {
this.text.set(text);
}
}

View file

@ -0,0 +1,155 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.TxConfidenceListener;
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
import io.bitsquare.util.Utilities;
import org.bitcoinj.core.TransactionConfidence;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TxIdTextField extends AnchorPane {
private static final Logger log = LoggerFactory.getLogger(TxIdTextField.class);
private final TextField textField;
private final Tooltip progressIndicatorTooltip;
private final ConfidenceProgressIndicator progressIndicator;
private final Label copyIcon;
private TxConfidenceListener txConfidenceListener;
private WalletService walletService;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public TxIdTextField() {
progressIndicator = new ConfidenceProgressIndicator();
progressIndicator.setFocusTraversable(false);
progressIndicator.setPrefSize(24, 24);
progressIndicator.setId("funds-confidence");
progressIndicator.setLayoutY(1);
progressIndicator.setProgress(0);
progressIndicator.setVisible(false);
AnchorPane.setRightAnchor(progressIndicator, 0.0);
progressIndicatorTooltip = new Tooltip("-");
Tooltip.install(progressIndicator, progressIndicatorTooltip);
copyIcon = new Label();
copyIcon.setLayoutY(3);
copyIcon.getStyleClass().add("copy-icon");
Tooltip.install(copyIcon, new Tooltip("Copy transaction ID to clipboard"));
AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY);
AnchorPane.setRightAnchor(copyIcon, 30.0);
textField = new TextField();
textField.setId("address-text-field");
textField.setEditable(false);
Tooltip.install(textField, new Tooltip("Open a blockchain explorer with that transactions ID"));
AnchorPane.setRightAnchor(textField, 55.0);
AnchorPane.setLeftAnchor(textField, 0.0);
textField.focusTraversableProperty().set(focusTraversableProperty().get());
focusedProperty().addListener((ov, oldValue, newValue) -> textField.requestFocus());
getChildren().addAll(textField, copyIcon, progressIndicator);
}
public void setup(WalletService walletService, String txID) {
this.walletService = walletService;
if (txConfidenceListener != null)
walletService.removeTxConfidenceListener(txConfidenceListener);
txConfidenceListener = new TxConfidenceListener(txID) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
updateConfidence(confidence);
}
};
walletService.addTxConfidenceListener(txConfidenceListener);
updateConfidence(walletService.getConfidenceForTxId(txID));
textField.setText(txID);
textField.setOnMouseClicked(mouseEvent -> {
try {
// TODO get the url form the app preferences
Utilities.openWebPage("https://www.biteasy.com/testnet/transactions/" + txID);
//Utilities.openURL("https://blockchain.info/tx/" + txID);
} catch (Exception e) {
log.error(e.getMessage());
Popups.openWarningPopup("Warning", "Opening browser failed. Please check your internet " +
"connection.");
}
});
copyIcon.setOnMouseClicked(e -> Utilities.copyToClipboard(txID));
}
public void cleanup() {
if (walletService != null && txConfidenceListener != null)
walletService.removeTxConfidenceListener(txConfidenceListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters/Setters
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void updateConfidence(TransactionConfidence confidence) {
if (confidence != null) {
switch (confidence.getConfidenceType()) {
case UNKNOWN:
progressIndicatorTooltip.setText("Unknown transaction status");
progressIndicator.setProgress(0);
break;
case PENDING:
progressIndicatorTooltip.setText(
"Seen by " + confidence.numBroadcastPeers() + " peer(s) / 0 " + "confirmations");
progressIndicator.setProgress(-1.0);
break;
case BUILDING:
progressIndicatorTooltip.setText("Confirmed in " + confidence.getDepthInBlocks() + " block(s)");
progressIndicator.setProgress(Math.min(1, (double) confidence.getDepthInBlocks() / 6.0));
break;
case DEAD:
progressIndicatorTooltip.setText("Transaction is invalid.");
progressIndicator.setProgress(0);
break;
}
if (progressIndicator.getProgress() != 0) {
progressIndicator.setVisible(true);
AnchorPane.setRightAnchor(progressIndicator, 0.0);
}
}
}
}

View file

@ -0,0 +1,256 @@
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package io.bitsquare.gui.components.confidence;
import io.bitsquare.gui.components.confidence.skin.ConfidenceProgressIndicatorSkin;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.css.PseudoClass;
import javafx.css.StyleableProperty;
import javafx.scene.control.*;
// TODO Copied form OpenJFX, check license issues and way how we integrated it
// We changed behaviour which was not exposed via APIs
/**
* A circular control which is used for indicating progress, either
* infinite (aka indeterminate) or finite. Often used with the Task API for
* representing progress of background Tasks.
* <p>
* ProgressIndicator sets focusTraversable to false.
* </p>
* <p/>
* <p/>
* This first example creates a ProgressIndicator with an indeterminate value :
* <pre><code>
* import javafx.scene.control.ProgressIndicator;
* ProgressIndicator p1 = new ProgressIndicator();
* </code></pre>
* <p/>
* <p/>
* This next example creates a ProgressIndicator which is 25% complete :
* <pre><code>
* import javafx.scene.control.ProgressIndicator;
* ProgressIndicator p2 = new ProgressIndicator();
* p2.setProgress(0.25F);
* </code></pre>
* <p/>
* Implementation of ProgressIndicator According to JavaFX UI Control API Specification
*
* @since JavaFX 2.0
*/
@SuppressWarnings({"SameParameterValue", "WeakerAccess"})
public class ConfidenceProgressIndicator extends Control {
/**
* Value for progress indicating that the progress is indeterminate.
*
* @see #setProgress
*/
public static final double INDETERMINATE_PROGRESS = -1;
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
/**
* Initialize the style class to 'progress-indicator'.
* <p/>
* This is the selector class from which CSS can be used to style
* this control.
*/
private static final String DEFAULT_STYLE_CLASS = "progress-indicator";
/**
* Pseudoclass indicating this is a determinate (i.e., progress can be
* determined) progress indicator.
*/
private static final PseudoClass PSEUDO_CLASS_DETERMINATE = PseudoClass.getPseudoClass("determinate");
/***************************************************************************
* *
* Properties *
* *
**************************************************************************/
/**
* Pseudoclass indicating this is an indeterminate (i.e., progress cannot
* be determined) progress indicator.
*/
private static final PseudoClass PSEUDO_CLASS_INDETERMINATE = PseudoClass.getPseudoClass("indeterminate");
/**
* A flag indicating whether it is possible to determine the progress
* of the ProgressIndicator. Typically indeterminate progress bars are
* rendered with some form of animation indicating potentially "infinite"
* progress.
*/
private ReadOnlyBooleanWrapper indeterminate;
/**
* The actual progress of the ProgressIndicator. A negative value for
* progress indicates that the progress is indeterminate. A positive value
* between 0 and 1 indicates the percentage of progress where 0 is 0% and 1
* is 100%. Any value greater than 1 is interpreted as 100%.
*/
private DoubleProperty progress;
/**
* Creates a new indeterminate ProgressIndicator.
*/
public ConfidenceProgressIndicator() {
this(INDETERMINATE_PROGRESS);
}
/**
* Creates a new ProgressIndicator with the given progress value.
*/
@SuppressWarnings("unchecked")
public ConfidenceProgressIndicator(double progress) {
// focusTraversable is styleable through css. Calling setFocusTraversable
// makes it look to css like the user set the value and css will not
// override. Initializing focusTraversable by calling applyStyle with null
// StyleOrigin ensures that css will be able to override the value.
((StyleableProperty) focusTraversableProperty()).applyStyle(null, Boolean.FALSE);
setProgress(progress);
getStyleClass().setAll(DEFAULT_STYLE_CLASS);
// need to initialize pseudo-class state
final int c = Double.compare(INDETERMINATE_PROGRESS, progress);
pseudoClassStateChanged(PSEUDO_CLASS_INDETERMINATE, c == 0);
pseudoClassStateChanged(PSEUDO_CLASS_DETERMINATE, c != 0);
}
public final boolean isIndeterminate() {
return indeterminate == null || indeterminate.get();
}
private void setIndeterminate(boolean value) {
indeterminatePropertyImpl().set(value);
}
public final ReadOnlyBooleanProperty indeterminateProperty() {
return indeterminatePropertyImpl().getReadOnlyProperty();
}
private ReadOnlyBooleanWrapper indeterminatePropertyImpl() {
if (indeterminate == null) {
indeterminate = new ReadOnlyBooleanWrapper(true) {
@Override
protected void invalidated() {
final boolean active = get();
pseudoClassStateChanged(PSEUDO_CLASS_INDETERMINATE, active);
pseudoClassStateChanged(PSEUDO_CLASS_DETERMINATE, !active);
}
@Override
public Object getBean() {
return ConfidenceProgressIndicator.this;
}
@Override
public String getName() {
return "indeterminate";
}
};
}
return indeterminate;
}
/**
* ************************************************************************
* *
* Methods *
* *
* ************************************************************************
*/
public final double getProgress() {
return progress == null ? INDETERMINATE_PROGRESS : progress.get();
}
/**
* ************************************************************************
* *
* Stylesheet Handling *
* *
* ************************************************************************
*/
public final void setProgress(double value) {
progressProperty().set(value);
}
public final DoubleProperty progressProperty() {
if (progress == null) {
progress = new DoublePropertyBase(-1.0) {
@Override
protected void invalidated() {
setIndeterminate(getProgress() < 0.0);
}
@Override
public Object getBean() {
return ConfidenceProgressIndicator.this;
}
@Override
public String getName() {
return "progress";
}
};
}
return progress;
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new ConfidenceProgressIndicatorSkin(this);
}
/**
* Most Controls return true for focusTraversable, so Control overrides
* this method to return true, but ProgressIndicator returns false for
* focusTraversable's initial value; hence the override of the override.
* This method is called from CSS code to get the correct initial value.
*/
@Deprecated
@Override
@SuppressWarnings("deprecation")
protected /*do not make final*/ Boolean impl_cssGetFocusTraversableInitialValue() {
return Boolean.FALSE;
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package io.bitsquare.gui.components.confidence.behavior;
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
import java.util.Collections;
import com.sun.javafx.scene.control.behavior.BehaviorBase;
// TODO Copied form OpenJFX, check license issues and way how we integrated it
// We changed behaviour which was not exposed via APIs
/**
* @param <C>
*/
public class ConfidenceProgressIndicatorBehavior<C extends ConfidenceProgressIndicator> extends BehaviorBase<C> {
/**
* ************************************************************************
* *
* Constructors *
* *
* ************************************************************************
*/
public ConfidenceProgressIndicatorBehavior(final C progress) {
super(progress, Collections.emptyList());
}
}

View file

@ -0,0 +1,829 @@
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package io.bitsquare.gui.components.confidence.skin;
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
import io.bitsquare.gui.components.confidence.behavior.ConfidenceProgressIndicatorBehavior;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.CssMetaData;
import javafx.css.StyleOrigin;
import javafx.css.Styleable;
import javafx.css.StyleableBooleanProperty;
import javafx.css.StyleableIntegerProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.geometry.Insets;
import javafx.geometry.NodeOrientation;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.transform.*;
import javafx.util.Duration;
import com.sun.javafx.css.converters.BooleanConverter;
import com.sun.javafx.css.converters.PaintConverter;
import com.sun.javafx.css.converters.SizeConverter;
import com.sun.javafx.scene.control.skin.BehaviorSkinBase;
// TODO Copied form OpenJFX, check license issues and way how we integrated it
// We changed behaviour which was not exposed via APIs
public class ConfidenceProgressIndicatorSkin extends BehaviorSkinBase<ConfidenceProgressIndicator,
ConfidenceProgressIndicatorBehavior<ConfidenceProgressIndicator>> {
/**
* ************************************************************************
* *
* UI Subcomponents *
* *
* ************************************************************************
*/
private IndeterminateSpinner spinner;
/**
* The number of segments in the spinner.
*/
private final IntegerProperty indeterminateSegmentCount = new StyleableIntegerProperty(8) {
@Override
protected void invalidated() {
if (spinner != null) {
spinner.rebuild();
}
}
@Override
public Object getBean() {
return ConfidenceProgressIndicatorSkin.this;
}
@Override
public String getName() {
return "indeterminateSegmentCount";
}
@Override
public CssMetaData<ConfidenceProgressIndicator, Number> getCssMetaData() {
return StyleableProperties.INDETERMINATE_SEGMENT_COUNT;
}
};
/**
* True if the progress indicator should rotate as well as animate opacity.
*/
private final BooleanProperty spinEnabled = new StyleableBooleanProperty(false) {
@Override
protected void invalidated() {
if (spinner != null) {
spinner.setSpinEnabled(get());
}
}
@Override
public CssMetaData<ConfidenceProgressIndicator, Boolean> getCssMetaData() {
return StyleableProperties.SPIN_ENABLED;
}
@Override
public Object getBean() {
return ConfidenceProgressIndicatorSkin.this;
}
@Override
public String getName() {
return "spinEnabled";
}
};
private DeterminateIndicator determinateIndicator;
/**
* The colour of the progress segment.
*/
private final ObjectProperty<Paint> progressColor = new StyleableObjectProperty<Paint>(null) {
@Override
public void set(Paint newProgressColor) {
final Paint color = (newProgressColor instanceof Color) ? newProgressColor : null;
super.set(color);
}
@Override
protected void invalidated() {
if (spinner != null) {
spinner.setFillOverride(get());
}
if (determinateIndicator != null) {
determinateIndicator.setFillOverride(get());
}
}
@Override
public Object getBean() {
return ConfidenceProgressIndicatorSkin.this;
}
@Override
public String getName() {
return "progressColorProperty";
}
@Override
public CssMetaData<ConfidenceProgressIndicator, Paint> getCssMetaData() {
return StyleableProperties.PROGRESS_COLOR;
}
};
private boolean timelineNulled = false;
/**
* ************************************************************************
* *
* Constructors *
* *
* ************************************************************************
*/
@SuppressWarnings("deprecation")
public ConfidenceProgressIndicatorSkin(ConfidenceProgressIndicator control) {
super(control, new ConfidenceProgressIndicatorBehavior<>(control));
InvalidationListener indeterminateListener = valueModel -> initialize();
control.indeterminateProperty().addListener(indeterminateListener);
InvalidationListener visibilityListener = new InvalidationListener() {
@Override
public void invalidated(Observable valueModel) {
if (getSkinnable().isIndeterminate() && timelineNulled && spinner == null) {
timelineNulled = false;
spinner = new IndeterminateSpinner(
getSkinnable(), ConfidenceProgressIndicatorSkin.this,
spinEnabled.get(), progressColor.get());
getChildren().add(spinner);
}
if (spinner != null) {
if (getSkinnable().impl_isTreeVisible() && getSkinnable().getScene() != null) {
spinner.indeterminateTimeline.play();
}
else {
spinner.indeterminateTimeline.pause();
getChildren().remove(spinner);
spinner = null;
timelineNulled = true;
}
}
}
};
control.visibleProperty().addListener(visibilityListener);
control.parentProperty().addListener(visibilityListener);
InvalidationListener sceneListener = new InvalidationListener() {
@Override
public void invalidated(Observable valueModel) {
if (spinner != null) {
if (getSkinnable().getScene() == null) {
spinner.indeterminateTimeline.pause();
getChildren().remove(spinner);
spinner = null;
timelineNulled = true;
}
}
else {
if (getSkinnable().getScene() != null && getSkinnable().isIndeterminate()) {
timelineNulled = false;
spinner = new IndeterminateSpinner(
getSkinnable(), ConfidenceProgressIndicatorSkin.this,
spinEnabled.get(), progressColor.get());
getChildren().add(spinner);
if (getSkinnable().impl_isTreeVisible()) {
spinner.indeterminateTimeline.play();
}
getSkinnable().requestLayout();
}
}
}
};
control.sceneProperty().addListener(sceneListener);
initialize();
getSkinnable().requestLayout();
}
/**
* @return The CssMetaData associated with this class, which may include the
* CssMetaData of its super classes.
*/
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
@SuppressWarnings("deprecation")
private void initialize() {
ConfidenceProgressIndicator control = getSkinnable();
boolean isIndeterminate = control.isIndeterminate();
if (isIndeterminate) {
// clean up determinateIndicator
determinateIndicator = null;
// create spinner
spinner = new IndeterminateSpinner(control, this, spinEnabled.get(), progressColor.get());
getChildren().clear();
getChildren().add(spinner);
if (getSkinnable().impl_isTreeVisible()) {
spinner.indeterminateTimeline.play();
}
}
else {
// clean up after spinner
if (spinner != null) {
spinner.indeterminateTimeline.stop();
spinner = null;
}
// create determinateIndicator
determinateIndicator =
new ConfidenceProgressIndicatorSkin.DeterminateIndicator(control, this, progressColor.get());
getChildren().clear();
getChildren().add(determinateIndicator);
}
}
@Override
public void dispose() {
super.dispose();
if (spinner != null) {
spinner.indeterminateTimeline.stop();
spinner = null;
}
}
@Override
protected void layoutChildren(final double x, final double y, final double w, final double h) {
if (spinner != null && getSkinnable().isIndeterminate()) {
spinner.layoutChildren();
spinner.resizeRelocate(0, 0, w, h);
}
else if (determinateIndicator != null) {
determinateIndicator.layoutChildren();
determinateIndicator.resizeRelocate(0, 0, w, h);
}
}
public Paint getProgressColor() {
return progressColor.get();
}
/**
* {@inheritDoc}
*/
@Override
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
return getClassCssMetaData();
}
// *********** Stylesheet Handling *****************************************
/**
* ************************************************************************
* *
* DeterminateIndicator *
* *
* ************************************************************************
*/
@SuppressWarnings({"SameReturnValue", "UnusedParameters"})
static class DeterminateIndicator extends Region {
//private double textGap = 2.0F;
private final ConfidenceProgressIndicator control;
//private Text text;
private final StackPane indicator;
private final StackPane progress;
private final StackPane tick;
private final Arc arcShape;
private final Circle indicatorCircle;
// only update progress text on whole percentages
private int intProgress;
// only update pie arc to nearest degree
private int degProgress;
public DeterminateIndicator(ConfidenceProgressIndicator control, ConfidenceProgressIndicatorSkin s,
Paint fillOverride) {
this.control = control;
getStyleClass().add("determinate-indicator");
intProgress = (int) Math.round(control.getProgress() * 100.0);
degProgress = (int) (360 * control.getProgress());
InvalidationListener progressListener = valueModel -> updateProgress();
control.progressProperty().addListener(progressListener);
getChildren().clear();
// The circular background for the progress pie piece
indicator = new StackPane();
indicator.setScaleShape(false);
indicator.setCenterShape(false);
indicator.getStyleClass().setAll("indicator");
indicatorCircle = new Circle();
indicator.setShape(indicatorCircle);
// The shape for our progress pie piece
arcShape = new Arc();
arcShape.setType(ArcType.ROUND);
arcShape.setStartAngle(90.0F);
// Our progress pie piece
progress = new StackPane();
progress.getStyleClass().setAll("progress");
progress.setScaleShape(false);
progress.setCenterShape(false);
progress.setShape(arcShape);
progress.getChildren().clear();
setFillOverride(fillOverride);
// The check mark that's drawn at 100%
tick = new StackPane();
tick.getStyleClass().setAll("tick");
getChildren().setAll(indicator, progress, /*text,*/ tick);
updateProgress();
}
private void setFillOverride(Paint fillOverride) {
if (fillOverride instanceof Color) {
Color c = (Color) fillOverride;
progress.setStyle("-fx-background-color: rgba(" + ((int) (255 * c.getRed())) + "," +
"" + ((int) (255 * c.getGreen())) + "," + ((int) (255 * c.getBlue())) + "," +
"" + c.getOpacity() + ");");
}
else {
progress.setStyle(null);
}
}
//@Override
public boolean isAutomaticallyMirrored() {
// This is used instead of setting NodeOrientation,
// allowing the Text node to inherit the current
// orientation.
return false;
}
private void updateProgress() {
intProgress = (int) Math.round(control.getProgress() * 100.0);
// text.setText((control.getProgress() >= 1) ? (DONE) : ("" + intProgress + "%"));
degProgress = (int) (360 * control.getProgress());
arcShape.setLength(-degProgress);
indicator.setOpacity(control.getProgress() == 0 ? 0 : 1);
requestLayout();
}
@Override
protected void layoutChildren() {
// Position and size the circular background
//double doneTextHeight = doneText.getLayoutBounds().getHeight();
final Insets controlInsets = control.getInsets();
final double left = snapSize(controlInsets.getLeft());
final double right = snapSize(controlInsets.getRight());
final double top = snapSize(controlInsets.getTop());
final double bottom = snapSize(controlInsets.getBottom());
/*
** use the min of width, or height, keep it a circle
*/
final double areaW = control.getWidth() - left - right;
final double areaH = control.getHeight() - top - bottom /*- textGap - doneTextHeight*/;
final double radiusW = areaW / 2;
final double radiusH = areaH / 2;
final double radius = Math.round(Math.min(radiusW, radiusH)); // use round instead of floor
final double centerX = snapPosition(left + radiusW);
final double centerY = snapPosition(top + radius);
// find radius that fits inside radius - insetsPadding
final Insets indicatorInsets = indicator.getInsets();
final double iLeft = snapSize(indicatorInsets.getLeft());
final double iRight = snapSize(indicatorInsets.getRight());
final double iTop = snapSize(indicatorInsets.getTop());
final double iBottom = snapSize(indicatorInsets.getBottom());
final double progressRadius = snapSize(Math.min(Math.min(radius - iLeft, radius - iRight),
Math.min(radius - iTop, radius - iBottom)));
indicatorCircle.setRadius(radius);
indicator.setLayoutX(centerX);
indicator.setLayoutY(centerY);
arcShape.setRadiusX(progressRadius);
arcShape.setRadiusY(progressRadius);
progress.setLayoutX(centerX);
progress.setLayoutY(centerY);
// find radius that fits inside progressRadius - progressInsets
final Insets progressInsets = progress.getInsets();
final double pLeft = snapSize(progressInsets.getLeft());
final double pRight = snapSize(progressInsets.getRight());
final double pTop = snapSize(progressInsets.getTop());
final double pBottom = snapSize(progressInsets.getBottom());
final double indicatorRadius = snapSize(Math.min(Math.min(progressRadius - pLeft,
progressRadius - pRight), Math.min(progressRadius - pTop, progressRadius - pBottom)));
// find size of spare box that fits inside indicator radius
double squareBoxHalfWidth = Math.ceil(Math.sqrt((indicatorRadius * indicatorRadius) / 2));
// double squareBoxHalfWidth2 = indicatorRadius * (Math.sqrt(2) / 2);
tick.setLayoutX(centerX - squareBoxHalfWidth);
tick.setLayoutY(centerY - squareBoxHalfWidth);
tick.resize(squareBoxHalfWidth + squareBoxHalfWidth, squareBoxHalfWidth + squareBoxHalfWidth);
tick.setVisible(control.getProgress() >= 1);
}
@Override
protected double computePrefWidth(double height) {
final Insets controlInsets = control.getInsets();
final double left = snapSize(controlInsets.getLeft());
final double right = snapSize(controlInsets.getRight());
final Insets indicatorInsets = indicator.getInsets();
final double iLeft = snapSize(indicatorInsets.getLeft());
final double iRight = snapSize(indicatorInsets.getRight());
final double iTop = snapSize(indicatorInsets.getTop());
final double iBottom = snapSize(indicatorInsets.getBottom());
final double indicatorMax = snapSize(Math.max(Math.max(iLeft, iRight), Math.max(iTop, iBottom)));
final Insets progressInsets = progress.getInsets();
final double pLeft = snapSize(progressInsets.getLeft());
final double pRight = snapSize(progressInsets.getRight());
final double pTop = snapSize(progressInsets.getTop());
final double pBottom = snapSize(progressInsets.getBottom());
final double progressMax = snapSize(Math.max(Math.max(pLeft, pRight), Math.max(pTop, pBottom)));
final Insets tickInsets = tick.getInsets();
final double tLeft = snapSize(tickInsets.getLeft());
final double tRight = snapSize(tickInsets.getRight());
final double indicatorWidth = indicatorMax + progressMax + tLeft + tRight + progressMax + indicatorMax;
return left + indicatorWidth + /*Math.max(indicatorWidth, doneText.getLayoutBounds().getWidth()) + */right;
}
@Override
protected double computePrefHeight(double width) {
final Insets controlInsets = control.getInsets();
final double top = snapSize(controlInsets.getTop());
final double bottom = snapSize(controlInsets.getBottom());
final Insets indicatorInsets = indicator.getInsets();
final double iLeft = snapSize(indicatorInsets.getLeft());
final double iRight = snapSize(indicatorInsets.getRight());
final double iTop = snapSize(indicatorInsets.getTop());
final double iBottom = snapSize(indicatorInsets.getBottom());
final double indicatorMax = snapSize(Math.max(Math.max(iLeft, iRight), Math.max(iTop, iBottom)));
final Insets progressInsets = progress.getInsets();
final double pLeft = snapSize(progressInsets.getLeft());
final double pRight = snapSize(progressInsets.getRight());
final double pTop = snapSize(progressInsets.getTop());
final double pBottom = snapSize(progressInsets.getBottom());
final double progressMax = snapSize(Math.max(Math.max(pLeft, pRight), Math.max(pTop, pBottom)));
final Insets tickInsets = tick.getInsets();
final double tTop = snapSize(tickInsets.getTop());
final double tBottom = snapSize(tickInsets.getBottom());
final double indicatorHeight = indicatorMax + progressMax + tTop + tBottom + progressMax + indicatorMax;
return top + indicatorHeight /*+ textGap + doneText.getLayoutBounds().getHeight()*/ + bottom;
}
@Override
protected double computeMaxWidth(double height) {
return computePrefWidth(height);
}
@Override
protected double computeMaxHeight(double width) {
return computePrefHeight(width);
}
}
/**
* ************************************************************************
* *
* IndeterminateSpinner *
* *
* ************************************************************************
*/
@SuppressWarnings("ConstantConditions")
static class IndeterminateSpinner extends Region {
private final ConfidenceProgressIndicator control;
private final ConfidenceProgressIndicatorSkin skin;
private final IndicatorPaths pathsG;
private final Timeline indeterminateTimeline;
private final List<Double> opacities = new ArrayList<>();
private boolean spinEnabled = false;
private Paint fillOverride = null;
public IndeterminateSpinner(ConfidenceProgressIndicator control, ConfidenceProgressIndicatorSkin s,
boolean spinEnabled, Paint fillOverride) {
this.control = control;
this.skin = s;
this.spinEnabled = spinEnabled;
this.fillOverride = fillOverride;
setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT);
getStyleClass().setAll("spinner");
pathsG = new IndicatorPaths(this);
getChildren().add(pathsG);
indeterminateTimeline = new Timeline();
indeterminateTimeline.setCycleCount(Timeline.INDEFINITE);
rebuildTimeline();
rebuild();
}
public void setFillOverride(Paint fillOverride) {
this.fillOverride = fillOverride;
rebuild();
}
public void setSpinEnabled(boolean spinEnabled) {
this.spinEnabled = spinEnabled;
rebuildTimeline();
}
private void rebuildTimeline() {
final ObservableList<KeyFrame> keyFrames = FXCollections.<KeyFrame>observableArrayList();
if (spinEnabled) {
keyFrames.add(new KeyFrame(Duration.millis(0), new KeyValue(pathsG.rotateProperty(), 360)));
keyFrames.add(new KeyFrame(Duration.millis(3900), new KeyValue(pathsG.rotateProperty(), 0)));
}
for (int i = 100; i <= 3900; i += 100) {
keyFrames.add(new KeyFrame(Duration.millis(i), event -> shiftColors()));
}
indeterminateTimeline.getKeyFrames().setAll(keyFrames);
}
private void pauseIndicator(boolean pause) {
if (indeterminateTimeline != null) {
if (pause) {
indeterminateTimeline.pause();
}
else {
indeterminateTimeline.play();
}
}
}
@Override
protected void layoutChildren() {
Insets controlInsets = control.getInsets();
final double w = control.getWidth() - controlInsets.getLeft() - controlInsets.getRight();
final double h = control.getHeight() - controlInsets.getTop() - controlInsets.getBottom();
final double prefW = pathsG.prefWidth(-1);
final double prefH = pathsG.prefHeight(-1);
double scaleX = w / prefW;
double scale = scaleX;
if ((scaleX * prefH) > h) {
scale = h / prefH;
}
double indicatorW = prefW * scale - 3;
double indicatorH = prefH * scale - 3;
pathsG.resizeRelocate((w - indicatorW) / 2, (h - indicatorH) / 2, indicatorW, indicatorH);
}
private void rebuild() {
// update indeterminate indicator
final int segments = skin.indeterminateSegmentCount.get();
opacities.clear();
pathsG.getChildren().clear();
final double step = 0.8 / (segments - 1);
for (int i = 0; i < segments; i++) {
Region region = new Region();
region.setScaleShape(false);
region.setCenterShape(false);
region.getStyleClass().addAll("segment", "segment" + i);
if (fillOverride instanceof Color) {
Color c = (Color) fillOverride;
region.setStyle("-fx-background-color: rgba(" + ((int) (255 * c.getRed())) + "," +
"" + ((int) (255 * c.getGreen())) + "," + ((int) (255 * c.getBlue())) + "," +
"" + c.getOpacity() + ");");
}
else {
region.setStyle(null);
}
pathsG.getChildren().add(region);
opacities.add(Math.min(1, 0.2 + (i * step)));
}
}
private void shiftColors() {
if (opacities.size() <= 0) {
return;
}
final int segments = skin.indeterminateSegmentCount.get();
Collections.rotate(opacities, -1);
for (int i = 0; i < segments; i++) {
pathsG.getChildren().get(i).setOpacity(opacities.get(i));
}
}
@SuppressWarnings("deprecation")
private class IndicatorPaths extends Pane {
final IndeterminateSpinner piSkin;
IndicatorPaths(IndeterminateSpinner pi) {
super();
piSkin = pi;
InvalidationListener treeVisibilityListener = valueModel -> {
if (piSkin.skin.getSkinnable().impl_isTreeVisible()) {
piSkin.pauseIndicator(false);
}
else {
piSkin.pauseIndicator(true);
}
};
impl_treeVisibleProperty().addListener(treeVisibilityListener);
}
@Override
protected double computePrefWidth(double height) {
double w = 0;
for (Node child : getChildren()) {
if (child instanceof Region) {
Region region = (Region) child;
if (region.getShape() != null) {
w = Math.max(w, region.getShape().getLayoutBounds().getMaxX());
}
else {
w = Math.max(w, region.prefWidth(height));
}
}
}
return w;
}
@Override
protected double computePrefHeight(double width) {
double h = 0;
for (Node child : getChildren()) {
if (child instanceof Region) {
Region region = (Region) child;
if (region.getShape() != null) {
h = Math.max(h, region.getShape().getLayoutBounds().getMaxY());
}
else {
h = Math.max(h, region.prefHeight(width));
}
}
}
return h;
}
@Override
protected void layoutChildren() {
// calculate scale
double scale = getWidth() / computePrefWidth(-1);
getChildren().stream().filter(child -> child instanceof Region).forEach(child -> {
Region region = (Region) child;
if (region.getShape() != null) {
region.resize(region.getShape().getLayoutBounds().getMaxX(),
region.getShape().getLayoutBounds().getMaxY());
region.getTransforms().setAll(new Scale(scale, scale, 0, 0));
}
else {
region.autosize();
}
});
}
}
}
/**
* Super-lazy instantiation pattern from Bill Pugh.
*/
@SuppressWarnings({"deprecation", "unchecked", "ConstantConditions"})
private static class StyleableProperties {
public static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
private static final CssMetaData<ConfidenceProgressIndicator, Paint> PROGRESS_COLOR =
new CssMetaData<ConfidenceProgressIndicator, Paint>(
"-fx-progress-color", PaintConverter.getInstance(), null) {
@Override
public boolean isSettable(ConfidenceProgressIndicator n) {
final ConfidenceProgressIndicatorSkin skin = (ConfidenceProgressIndicatorSkin) n.getSkin();
return skin.progressColor == null || !skin.progressColor.isBound();
}
@Override
public StyleableProperty<Paint> getStyleableProperty(ConfidenceProgressIndicator n) {
final ConfidenceProgressIndicatorSkin skin = (ConfidenceProgressIndicatorSkin) n.getSkin();
return (StyleableProperty<Paint>) skin.progressColor;
}
};
private static final CssMetaData<ConfidenceProgressIndicator, Number> INDETERMINATE_SEGMENT_COUNT =
new CssMetaData<ConfidenceProgressIndicator, Number>(
"-fx-indeterminate-segment-count", SizeConverter.getInstance(), 8) {
@Override
public void set(ConfidenceProgressIndicator node, Number value, StyleOrigin origin) {
super.set(node, value.intValue(), origin);
}
@Override
public boolean isSettable(ConfidenceProgressIndicator n) {
final ConfidenceProgressIndicatorSkin skin = (ConfidenceProgressIndicatorSkin) n.getSkin();
return skin.indeterminateSegmentCount == null || !skin.indeterminateSegmentCount.isBound();
}
@Override
public StyleableProperty<Number> getStyleableProperty(ConfidenceProgressIndicator n) {
final ConfidenceProgressIndicatorSkin skin = (ConfidenceProgressIndicatorSkin) n.getSkin();
return (StyleableProperty<Number>) skin.indeterminateSegmentCount;
}
};
private static final CssMetaData<ConfidenceProgressIndicator, Boolean> SPIN_ENABLED = new
CssMetaData<ConfidenceProgressIndicator, Boolean>("-fx-spin-enabled",
BooleanConverter.getInstance(),
Boolean.FALSE) {
@Override
public boolean isSettable(ConfidenceProgressIndicator node) {
final ConfidenceProgressIndicatorSkin skin = (ConfidenceProgressIndicatorSkin) node.getSkin();
return skin.spinEnabled == null || !skin.spinEnabled.isBound();
}
@Override
public StyleableProperty<Boolean> getStyleableProperty(ConfidenceProgressIndicator node) {
final ConfidenceProgressIndicatorSkin skin = (ConfidenceProgressIndicatorSkin) node.getSkin();
return (StyleableProperty<Boolean>) skin.spinEnabled;
}
};
static {
final List<CssMetaData<? extends Styleable, ?>> styleables =
new ArrayList<>(SkinBase.getClassCssMetaData());
styleables.add(PROGRESS_COLOR);
styleables.add(INDETERMINATE_SEGMENT_COUNT);
styleables.add(SPIN_ENABLED);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
}

View file

@ -0,0 +1,95 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components.processbar;
import java.util.List;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.control.*;
public class ProcessStepBar<T> extends Control {
private List<ProcessStepItem> processStepItems;
private final IntegerProperty selectedIndex = new SimpleIntegerProperty(0);
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public ProcessStepBar() {
}
public ProcessStepBar(List<ProcessStepItem> processStepItems) {
this.processStepItems = processStepItems;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Methods
///////////////////////////////////////////////////////////////////////////////////////////
public void next() {
setSelectedIndex(getSelectedIndex() + 1);
}
@Override
protected Skin<?> createDefaultSkin() {
return new ProcessStepBarSkin<>(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setProcessStepItems(List<ProcessStepItem> processStepItems) {
this.processStepItems = processStepItems;
if (getSkin() != null)
((ProcessStepBarSkin) getSkin()).setProcessStepItems(processStepItems);
}
public void setSelectedIndex(int selectedIndex) {
this.selectedIndex.set(selectedIndex);
if (getSkin() != null)
((ProcessStepBarSkin) getSkin()).setSelectedIndex(selectedIndex);
}
public void reset() {
if (getSkin() != null)
((ProcessStepBarSkin) getSkin()).reset();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public List<ProcessStepItem> getProcessStepItems() {
return processStepItems;
}
public int getSelectedIndex() {
return selectedIndex.get();
}
public IntegerProperty selectedIndexProperty() {
return selectedIndex;
}
}

View file

@ -0,0 +1,242 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components.processbar;
import io.bitsquare.gui.util.Colors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import com.sun.javafx.scene.control.behavior.BehaviorBase;
import com.sun.javafx.scene.control.behavior.KeyBinding;
import com.sun.javafx.scene.control.skin.BehaviorSkinBase;
class ProcessStepBarSkin<T> extends BehaviorSkinBase<ProcessStepBar<T>, BehaviorBase<ProcessStepBar<T>>> {
private final ProcessStepBar<T> controller;
private LabelWithBorder currentLabelWithBorder;
private LabelWithBorder prevLabelWithBorder;
private final List<LabelWithBorder> labelWithBorders = new ArrayList<>();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public ProcessStepBarSkin(final ProcessStepBar<T> control) {
super(control, new BehaviorBase<>(control, Collections.<KeyBinding>emptyList()));
controller = getSkinnable();
setProcessStepItems(controller.getProcessStepItems());
setSelectedIndex(controller.getSelectedIndex());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Methods
///////////////////////////////////////////////////////////////////////////////////////////
public void reset() {
prevLabelWithBorder = null;
for (LabelWithBorder labelWithBorder : labelWithBorders) {
currentLabelWithBorder = labelWithBorder;
currentLabelWithBorder.open();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setProcessStepItems(List<ProcessStepItem> processStepItems) {
if (processStepItems != null) {
int i = 0;
int size = controller.getProcessStepItems().size();
for (ProcessStepItem processStepItem : controller.getProcessStepItems()) {
LabelWithBorder labelWithBorder = new LabelWithBorder(processStepItem, i == 0, i == size - 1);
getChildren().add(labelWithBorder);
labelWithBorders.add(labelWithBorder);
if (i == 0)
currentLabelWithBorder = prevLabelWithBorder = labelWithBorder;
i++;
}
currentLabelWithBorder.current();
}
}
public void setSelectedIndex(int index) {
if (index < labelWithBorders.size()) {
for (int i = 0; i <= index; i++) {
if (prevLabelWithBorder != null)
prevLabelWithBorder.past();
currentLabelWithBorder = labelWithBorders.get(i);
currentLabelWithBorder.current();
prevLabelWithBorder = currentLabelWithBorder;
}
}
}
@Override
protected void layoutChildren(double x, double y, double width, double height) {
double distance = 10;
double padding = 50;
for (int i = 0; i < getChildren().size(); i++) {
Node node = getChildren().get(i);
double newWidth = snapSize(node.prefWidth(height)) + padding;
double newHeight = snapSize(node.prefHeight(-1) + 10);
if (i > 0)
x = x - ((LabelWithBorder) node).getArrowWidth();
node.resize(newWidth, newHeight);
// need to add 0.5 to make it sharp
node.relocate(x + 0.5, 0.5);
x += newWidth + distance;
}
}
public static class LabelWithBorder extends Label {
final double borderWidth = 1;
private final double arrowWidth = 10;
private final double arrowHeight = 30;
private final ProcessStepItem processStepItem;
private final boolean isFirst;
private final boolean isLast;
public LabelWithBorder(ProcessStepItem processStepItem, boolean isFirst, boolean isLast) {
super(processStepItem.getLabel());
this.processStepItem = processStepItem;
this.isFirst = isFirst;
this.isLast = isLast;
setAlignment(Pos.CENTER);
setStyle("-fx-font-size: 14");
this.setShape(createButtonShape());
open();
}
public void open() {
BorderStroke borderStroke = new BorderStroke(Colors.LIGHT_GREY, BorderStrokeStyle.SOLID, null,
new BorderWidths(borderWidth, borderWidth, borderWidth, borderWidth), Insets.EMPTY);
this.setBorder(new Border(borderStroke));
setTextFill(Colors.LIGHT_GREY);
}
public void current() {
BorderStroke borderStroke = new BorderStroke(Colors.GREEN, BorderStrokeStyle.SOLID, null,
new BorderWidths(borderWidth, borderWidth, borderWidth, borderWidth), Insets.EMPTY);
this.setBorder(new Border(borderStroke));
setTextFill(Colors.GREEN);
}
public void past() {
BorderStroke borderStroke = new BorderStroke(Color.valueOf("#444444"), BorderStrokeStyle.SOLID, null,
new BorderWidths(borderWidth, borderWidth, borderWidth, borderWidth), Insets.EMPTY);
this.setBorder(new Border(borderStroke));
setTextFill(Color.valueOf("#444444"));
}
public double getArrowWidth() {
return arrowWidth;
}
private Path createButtonShape() {
// build the following shape (or home without left arrow)
// --------
// \ \
// / /
// --------
Path path = new Path();
// begin in the upper left corner
MoveTo e1 = new MoveTo(0, 0);
path.getElements().add(e1);
// draw a horizontal line that defines the width of the shape
HLineTo e2 = new HLineTo();
// bind the width of the shape to the width of the button
e2.xProperty().bind(this.widthProperty().subtract(arrowWidth));
path.getElements().add(e2);
if (!isLast) {
// draw upper part of right arrow
LineTo e3 = new LineTo();
// the x endpoint of this line depends on the x property of line e2
e3.xProperty().bind(e2.xProperty().add(arrowWidth));
e3.setY(arrowHeight / 2.0);
path.getElements().add(e3);
}
// draw lower part of right arrow
LineTo e4 = new LineTo();
// the x endpoint of this line depends on the x property of line e2
e4.xProperty().bind(e2.xProperty());
e4.setY(arrowHeight);
path.getElements().add(e4);
// draw lower horizontal line
HLineTo e5 = new HLineTo(0);
path.getElements().add(e5);
if (!isFirst) {
LineTo e6 = new LineTo(arrowWidth, arrowHeight / 2.0);
path.getElements().add(e6);
}
// close path
ClosePath e7 = new ClosePath();
path.getElements().add(e7);
// this is a dummy color to fill the shape, it won't be visible
path.setFill(Color.BLACK);
return path;
}
@Override
public String toString() {
return "LabelWithBorder{" +
", processStepItem=" + processStepItem +
'}';
}
}
}

View file

@ -0,0 +1,31 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components.processbar;
public class ProcessStepItem {
private final String label;
public ProcessStepItem(String label) {
this.label = label;
}
String getLabel() {
return label;
}
}

View file

@ -0,0 +1,172 @@
/* splash screen */
/*noinspection CssUnknownTarget*/
#image-splash-logo {
-fx-image: url("../../../images/logo_splash.png");
}
/* notification */
#notification-logo {
-fx-image: url("../../../images/notification_logo.png");
}
/* shared*/
#image-info {
-fx-image: url("../../../images/info.png");
}
#image-alert-round {
-fx-image: url("../../../images/alert_round.png");
}
#image-remove {
-fx-image: url("../../../images/remove.png");
}
#image-buy {
-fx-image: url("../../../images/buy.png");
}
#image-sell {
-fx-image: url("../../../images/sell.png");
}
#image-expand {
-fx-image: url("../../../images/expand.png");
}
#image-collapse {
-fx-image: url("../../../images/collapse.png");
}
#image-buy-large {
-fx-image: url("../../../images/buy_large.png");
}
#image-sell-large {
-fx-image: url("../../../images/sell_large.png");
}
/* navigation buttons */
#image-nav-home {
-fx-image: url("../../../images/nav/home.png");
}
#image-nav-home-active {
-fx-image: url("../../../images/nav/home_active.png");
}
#image-nav-buyoffer {
-fx-image: url("../../../images/nav/buyoffer.png");
}
#image-nav-buyoffer-active {
-fx-image: url("../../../images/nav/buyoffer_active.png");
}
#image-nav-selloffer {
-fx-image: url("../../../images/nav/selloffer.png");
}
#image-nav-selloffer-active {
-fx-image: url("../../../images/nav/selloffer_active.png");
}
#image-nav-portfolio {
-fx-image: url("../../../images/nav/portfolio.png");
}
#image-nav-portfolio-active {
-fx-image: url("../../../images/nav/portfolio_active.png");
}
#image-nav-funds {
-fx-image: url("../../../images/nav/funds.png");
}
#image-nav-funds-active {
-fx-image: url("../../../images/nav/funds_active.png");
}
#image-nav-msg {
-fx-image: url("../../../images/nav/msg.png");
}
#image-nav-msg-active {
-fx-image: url("../../../images/nav/msg_active.png");
}
#image-nav-settings {
-fx-image: url("../../../images/nav/settings.png");
}
#image-nav-settings-active {
-fx-image: url("../../../images/nav/settings_active.png");
}
#image-nav-account {
-fx-image: url("../../../images/nav/account.png");
}
#image-nav-account-active {
-fx-image: url("../../../images/nav/account_active.png");
}
/* account*/
#image-tick {
-fx-image: url("../../../images/tick.png");
}
#image-arrow-blue {
-fx-image: url("../../../images/arrow_blue.png");
}
#image-arrow-grey {
-fx-image: url("../../../images/arrow_grey.png");
}
/* connection state*/
#image-connection-direct {
-fx-image: url("../../../images/connection/direct.png");
}
#image-connection-nat {
-fx-image: url("../../../images/connection/nat.png");
}
#image-connection-relay {
-fx-image: url("../../../images/connection/relay.png");
}
#image-connection-synced {
-fx-image: url("../../../images/connection/synced.png");
}
/* software update*/
#image-update-in-progress {
-fx-image: url("../../../images/update/update_in_progress.png");
}
#image-update-up-to-date {
-fx-image: url("../../../images/update/update_up_to_date.png");
}
#image-update-available {
-fx-image: url("../../../images/update/update_available.png");
}
#image-update-failed {
-fx-image: url("../../../images/update/update_failed.png");
}
/* offer state */
#image-offer_state_unknown {
-fx-image: url("../../../images/offer/offer_state_unknown.png");
}
#image-offer_state_available {
-fx-image: url("../../../images/offer/offer_state_available.png");
}
#image-offer_state_not_available {
-fx-image: url("../../../images/offer/offer_state_not_available.png");
}

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.*?>
<StackPane fx:id="root" fx:controller="io.bitsquare.gui.main.MainView"
prefHeight="750" prefWidth="1000" stylesheets="/io/bitsquare/gui/bitsquare.css"
xmlns:fx="http://javafx.com/fxml">
</StackPane>

View file

@ -0,0 +1,398 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main;
import io.bitsquare.BitsquareException;
import io.bitsquare.common.view.CachingViewLoader;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.InitializableView;
import io.bitsquare.common.view.View;
import io.bitsquare.common.view.ViewLoader;
import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.OverlayManager;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.components.SystemNotification;
import io.bitsquare.gui.main.account.AccountView;
import io.bitsquare.gui.main.funds.FundsView;
import io.bitsquare.gui.main.home.HomeView;
import io.bitsquare.gui.main.msg.MsgView;
import io.bitsquare.gui.main.offer.BuyOfferView;
import io.bitsquare.gui.main.offer.SellOfferView;
import io.bitsquare.gui.main.portfolio.PortfolioView;
import io.bitsquare.gui.main.settings.SettingsView;
import io.bitsquare.gui.util.Transitions;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import static javafx.scene.layout.AnchorPane.*;
@FxmlView
public class MainView extends InitializableView<StackPane, MainViewModel> {
public static final String TITLE_KEY = "view.title";
private final ToggleGroup navButtons = new ToggleGroup();
private final ViewLoader viewLoader;
private final Navigation navigation;
private final OverlayManager overlayManager;
private final Transitions transitions;
private final String title;
@Inject
public MainView(MainViewModel model, CachingViewLoader viewLoader, Navigation navigation, OverlayManager overlayManager, Transitions transitions,
@Named(MainView.TITLE_KEY) String title) {
super(model);
this.viewLoader = viewLoader;
this.navigation = navigation;
this.overlayManager = overlayManager;
this.transitions = transitions;
this.title = title;
}
@Override
protected void initialize() {
ToggleButton homeButton = new NavButton(HomeView.class, "Overview") {{
setDisable(true); // alpha
}};
ToggleButton buyButton = new NavButton(BuyOfferView.class, "Buy BTC");
ToggleButton sellButton = new NavButton(SellOfferView.class, "Sell BTC");
ToggleButton portfolioButton = new NavButton(PortfolioView.class, "Portfolio");
ToggleButton fundsButton = new NavButton(FundsView.class, "Funds");
ToggleButton msgButton = new NavButton(MsgView.class, "Messages") {{
setDisable(true); // alpha
}};
ToggleButton settingsButton = new NavButton(SettingsView.class, "Settings");
ToggleButton accountButton = new NavButton(AccountView.class, "Account"); /*{{
setDisable(true); // alpha
}};*/
Pane portfolioButtonHolder = new Pane(portfolioButton);
Pane bankAccountComboBoxHolder = new Pane();
HBox leftNavPane = new HBox(homeButton, buyButton, sellButton, portfolioButtonHolder, fundsButton, new Pane(msgButton)) {{
setSpacing(10);
setLeftAnchor(this, 10d);
setTopAnchor(this, 0d);
}};
HBox rightNavPane = new HBox(bankAccountComboBoxHolder, settingsButton, accountButton) {{
setSpacing(10);
setRightAnchor(this, 10d);
setTopAnchor(this, 0d);
}};
AnchorPane contentContainer = new AnchorPane() {{
setId("content-pane");
setLeftAnchor(this, 0d);
setRightAnchor(this, 0d);
setTopAnchor(this, 60d);
setBottomAnchor(this, 25d);
}};
AnchorPane applicationContainer = new AnchorPane(leftNavPane, rightNavPane, contentContainer) {{
setId("content-pane");
}};
BorderPane baseApplicationContainer = new BorderPane(applicationContainer) {{
setId("base-content-container");
}};
setupNotificationIcon(portfolioButtonHolder);
navigation.addListener(viewPath -> {
if (viewPath.size() != 2 || viewPath.indexOf(MainView.class) != 0)
return;
Class<? extends View> viewClass = viewPath.tip();
View view = viewLoader.load(viewClass);
contentContainer.getChildren().setAll(view.getRoot());
navButtons.getToggles().stream()
.filter(toggle -> toggle instanceof NavButton)
.filter(button -> viewClass == ((NavButton) button).viewClass)
.findFirst()
.orElseThrow(() -> new BitsquareException("No button matching %s found", viewClass))
.setSelected(true);
});
configureBlurring(baseApplicationContainer);
VBox splashScreen = createSplashScreen();
root.getChildren().addAll(baseApplicationContainer, splashScreen);
model.showAppScreen.addListener((ov, oldValue, newValue) -> {
if (newValue) {
bankAccountComboBoxHolder.getChildren().setAll(createBankAccountComboBox());
navigation.navigateToPreviousVisitedView();
transitions.fadeOutAndRemove(splashScreen, 1500);
}
});
// Delay a bit to give time for rendering the splash screen
Platform.runLater(model::initBackend);
}
private void setupNotificationIcon(Pane portfolioButtonHolder) {
Label numPendingTradesLabel = new Label();
numPendingTradesLabel.textProperty().bind(model.numPendingTradesAsString);
numPendingTradesLabel.relocate(5, 1);
numPendingTradesLabel.setId("nav-alert-label");
ImageView icon = new ImageView();
icon.setLayoutX(0.5);
icon.setId("image-alert-round");
Pane notification = new Pane();
notification.relocate(30, 9);
notification.setMouseTransparent(true);
notification.setEffect(new DropShadow(4, 1, 2, Color.GREY));
notification.getChildren().addAll(icon, numPendingTradesLabel);
notification.visibleProperty().bind(model.showPendingTradesNotification);
portfolioButtonHolder.getChildren().add(notification);
model.showPendingTradesNotification.addListener((ov, oldValue, newValue) -> {
if (newValue)
SystemNotification.openInfoNotification(title, "You got a new trade message.");
});
}
private VBox createSplashScreen() {
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
vBox.setSpacing(0);
vBox.setId("splash");
ImageView logo = new ImageView();
logo.setId("image-splash-logo");
Label blockchainSyncLabel = new Label();
blockchainSyncLabel.textProperty().bind(model.blockchainSyncInfo);
model.walletServiceErrorMsg.addListener((ov, oldValue, newValue) -> {
blockchainSyncLabel.setId("splash-error-state-msg");
Popups.openErrorPopup("Error", "Connecting to the bitcoin network failed. \n\nReason: " +
newValue);
});
ProgressBar blockchainSyncIndicator = new ProgressBar(-1);
blockchainSyncIndicator.setPrefWidth(120);
blockchainSyncIndicator.progressProperty().bind(model.blockchainSyncProgress);
ImageView blockchainSyncIcon = new ImageView();
blockchainSyncIcon.setVisible(false);
blockchainSyncIcon.setManaged(false);
model.blockchainSyncIconId.addListener((ov, oldValue, newValue) -> {
blockchainSyncIcon.setId(newValue);
blockchainSyncIcon.setVisible(true);
blockchainSyncIcon.setManaged(true);
blockchainSyncIndicator.setVisible(false);
blockchainSyncIndicator.setManaged(false);
});
Label bitcoinNetworkLabel = new Label();
bitcoinNetworkLabel.setText(model.bitcoinNetworkAsString);
bitcoinNetworkLabel.setId("splash-bitcoin-network-label");
HBox blockchainSyncBox = new HBox();
blockchainSyncBox.setSpacing(10);
blockchainSyncBox.setAlignment(Pos.CENTER);
blockchainSyncBox.setPadding(new Insets(40, 0, 0, 0));
blockchainSyncBox.setPrefHeight(50);
blockchainSyncBox.getChildren().addAll(blockchainSyncLabel, blockchainSyncIndicator,
blockchainSyncIcon, bitcoinNetworkLabel);
Label bootstrapStateLabel = new Label();
bootstrapStateLabel.setWrapText(true);
bootstrapStateLabel.setMaxWidth(500);
bootstrapStateLabel.setTextAlignment(TextAlignment.CENTER);
bootstrapStateLabel.textProperty().bind(model.bootstrapInfo);
ProgressIndicator bootstrapIndicator = new ProgressIndicator();
bootstrapIndicator.setMaxSize(24, 24);
bootstrapIndicator.progressProperty().bind(model.bootstrapProgress);
model.bootstrapErrorMsg.addListener((ov, oldValue, newValue) -> {
bootstrapStateLabel.setId("splash-error-state-msg");
bootstrapIndicator.setVisible(false);
Popups.openErrorPopup("Error", "Connecting to the Bitsquare network failed. \n\nReason: " +
model.bootstrapErrorMsg.get());
});
ImageView bootstrapIcon = new ImageView();
bootstrapIcon.setVisible(false);
bootstrapIcon.setManaged(false);
model.bootstrapIconId.addListener((ov, oldValue, newValue) -> {
bootstrapIcon.setId(newValue);
bootstrapIcon.setVisible(true);
bootstrapIcon.setManaged(true);
bootstrapIndicator.setVisible(false);
bootstrapIndicator.setManaged(false);
});
HBox bootstrapBox = new HBox();
bootstrapBox.setSpacing(10);
bootstrapBox.setAlignment(Pos.CENTER);
bootstrapBox.setPrefHeight(50);
bootstrapBox.getChildren().addAll(bootstrapStateLabel, bootstrapIndicator, bootstrapIcon);
// software update
Label updateInfoLabel = new Label();
updateInfoLabel.setTextAlignment(TextAlignment.RIGHT);
updateInfoLabel.textProperty().bind(model.updateInfo);
Button restartButton = new Button("Restart");
restartButton.setDefaultButton(true);
restartButton.visibleProperty().bind(model.showRestartButton);
restartButton.managedProperty().bind(model.showRestartButton);
restartButton.setOnAction(e -> model.restart());
ImageView updateIcon = new ImageView();
updateIcon.setId(model.updateIconId.get());
model.updateIconId.addListener((ov, oldValue, newValue) -> {
updateIcon.setId(newValue);
updateIcon.setVisible(true);
updateIcon.setManaged(true);
});
HBox updateBox = new HBox();
updateBox.setSpacing(10);
updateBox.setAlignment(Pos.CENTER);
updateBox.setPrefHeight(20);
updateBox.getChildren().addAll(updateInfoLabel, restartButton, updateIcon);
vBox.getChildren().addAll(logo, blockchainSyncBox, bootstrapBox, updateBox);
return vBox;
}
private VBox createBankAccountComboBox() {
final ComboBox<FiatAccount> comboBox = new ComboBox<>(model.getBankAccounts());
comboBox.setLayoutY(12);
comboBox.setVisibleRowCount(5);
comboBox.setConverter(model.getBankAccountsConverter());
comboBox.disableProperty().bind(model.bankAccountsComboBoxDisable);
comboBox.promptTextProperty().bind(model.bankAccountsComboBoxPrompt);
comboBox.getSelectionModel().selectedItemProperty().addListener((ov, oldValue, newValue) ->
model.setCurrentBankAccount(newValue));
model.currentBankAccount.addListener((ov, oldValue, newValue) ->
comboBox.getSelectionModel().select(newValue));
comboBox.getSelectionModel().select(model.currentBankAccount.get());
final Label titleLabel = new Label("Bank account");
titleLabel.setMouseTransparent(true);
titleLabel.setId("nav-button-label");
comboBox.widthProperty().addListener((ov, o, n) ->
titleLabel.setLayoutX(((double) n - titleLabel.getWidth()) / 2));
VBox vBox = new VBox();
vBox.setPadding(new Insets(12, 8, 0, 5));
vBox.setSpacing(2);
vBox.setAlignment(Pos.CENTER);
vBox.getChildren().setAll(comboBox, titleLabel);
// For alpha
vBox.setDisable(true);
return vBox;
}
private void configureBlurring(Node node) {
Popups.setOverlayManager(overlayManager);
overlayManager.addListener(new OverlayManager.OverlayListener() {
@Override
public void onBlurContentRequested() {
transitions.blur(node);
}
@Override
public void onRemoveBlurContentRequested() {
transitions.removeBlur(node);
}
});
}
private class NavButton extends ToggleButton {
private final Class<? extends View> viewClass;
public NavButton(Class<? extends View> viewClass, String title) {
super(title, new ImageView() {{
setId("image-nav-" + viewId(viewClass));
}});
this.viewClass = viewClass;
this.setToggleGroup(navButtons);
this.setId("nav-button");
this.setPadding(new Insets(0, -10, -10, -10));
this.setMinSize(50, 50);
this.setMaxSize(50, 50);
this.setContentDisplay(ContentDisplay.TOP);
this.setGraphicTextGap(0);
this.selectedProperty().addListener((ov, oldValue, newValue) -> {
this.setMouseTransparent(newValue);
this.setMinSize(50, 50);
this.setMaxSize(50, 50);
this.setGraphicTextGap(newValue ? -1 : 0);
if (newValue) {
this.getGraphic().setId("image-nav-" + viewId(viewClass) + "-active");
}
else {
this.getGraphic().setId("image-nav-" + viewId(viewClass));
}
});
this.setOnAction(e -> navigation.navigateTo(MainView.class, viewClass));
}
}
private static String viewId(Class<? extends View> viewClass) {
String viewName = viewClass.getSimpleName();
String suffix = "View";
int suffixIdx = viewName.indexOf(suffix);
if (suffixIdx != viewName.length() - suffix.length())
throw new IllegalArgumentException("Cannot get ID for " + viewClass + ": class must end in " + suffix);
return viewName.substring(0, suffixIdx).toLowerCase();
}
}

View file

@ -0,0 +1,330 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main;
import io.bitsquare.app.UpdateProcess;
import io.bitsquare.arbitration.ArbitrationRepository;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.model.ViewModel;
import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.CountryUtil;
import io.bitsquare.p2p.BaseP2PService;
import io.bitsquare.p2p.BootstrapState;
import io.bitsquare.p2p.ClientNode;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.user.User;
import com.google.inject.Inject;
import java.util.concurrent.TimeoutException;
import javafx.application.Platform;
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.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
class MainViewModel implements ViewModel {
private static final Logger log = LoggerFactory.getLogger(MainViewModel.class);
// BTC network
final StringProperty blockchainSyncInfo = new SimpleStringProperty("Initializing");
final DoubleProperty blockchainSyncProgress = new SimpleDoubleProperty(-1);
final StringProperty walletServiceErrorMsg = new SimpleStringProperty();
final StringProperty blockchainSyncIconId = new SimpleStringProperty();
// P2P network
final StringProperty bootstrapInfo = new SimpleStringProperty();
final DoubleProperty bootstrapProgress = new SimpleDoubleProperty(-1);
final StringProperty bootstrapErrorMsg = new SimpleStringProperty();
final StringProperty bootstrapIconId = new SimpleStringProperty();
// software update
final StringProperty updateInfo = new SimpleStringProperty();
final BooleanProperty showRestartButton = new SimpleBooleanProperty(false);
final StringProperty updateIconId = new SimpleStringProperty();
final StringProperty bankAccountsComboBoxPrompt = new SimpleStringProperty();
final BooleanProperty bankAccountsComboBoxDisable = new SimpleBooleanProperty();
final ObjectProperty<FiatAccount> currentBankAccount = new SimpleObjectProperty<>();
final BooleanProperty showAppScreen = new SimpleBooleanProperty();
final StringProperty numPendingTradesAsString = new SimpleStringProperty();
final BooleanProperty showPendingTradesNotification = new SimpleBooleanProperty();
final String bitcoinNetworkAsString;
private final User user;
private final WalletService walletService;
private final ArbitrationRepository arbitrationRepository;
private final ClientNode clientNode;
private final TradeManager tradeManager;
private final UpdateProcess updateProcess;
private final BSFormatter formatter;
@Inject
public MainViewModel(User user, WalletService walletService, ArbitrationRepository arbitrationRepository, ClientNode clientNode,
TradeManager tradeManager, BitcoinNetwork bitcoinNetwork, UpdateProcess updateProcess,
BSFormatter formatter) {
this.user = user;
this.walletService = walletService;
this.arbitrationRepository = arbitrationRepository;
this.clientNode = clientNode;
this.tradeManager = tradeManager;
this.updateProcess = updateProcess;
this.formatter = formatter;
bitcoinNetworkAsString = bitcoinNetwork.toString();
updateProcess.state.addListener((observableValue, oldValue, newValue) -> applyUpdateState(newValue));
applyUpdateState(updateProcess.state.get());
currentBankAccount.bind(user.currentFiatAccountProperty());
user.fiatAccountsObservableList().addListener((ListChangeListener<FiatAccount>) change -> {
bankAccountsComboBoxDisable.set(change.getList().isEmpty());
bankAccountsComboBoxPrompt.set(change.getList().isEmpty() ? "No accounts" : "");
});
bankAccountsComboBoxDisable.set(user.fiatAccountsObservableList().isEmpty());
bankAccountsComboBoxPrompt.set(user.fiatAccountsObservableList().isEmpty() ? "No accounts" : "");
}
public void restart() {
updateProcess.restart();
}
public void initBackend() {
Platform.runLater(updateProcess::init);
setBitcoinNetworkSyncProgress(-1);
walletService.getDownloadProgress().subscribe(
percentage -> Platform.runLater(() -> {
if (percentage > 0)
setBitcoinNetworkSyncProgress(percentage / 100.0);
}),
error -> log.error(error.toString()),
() -> Platform.runLater(() -> setBitcoinNetworkSyncProgress(1.0)));
// Set executor for all P2PServices
BaseP2PService.setUserThread(Platform::runLater);
Observable<BootstrapState> bootstrapStateAsObservable = clientNode.bootstrap(user.getP2pSigKeyPair());
bootstrapStateAsObservable.publish();
bootstrapStateAsObservable.subscribe(
state -> Platform.runLater(() -> setBootstrapState(state)),
error -> Platform.runLater(() -> {
log.error(error.toString());
bootstrapErrorMsg.set(error.getMessage());
bootstrapInfo.set("Connecting to the Bitsquare network failed.");
bootstrapProgress.set(0);
}),
() -> log.trace("message completed"));
Observable<Object> walletServiceObservable = walletService.initialize(Platform::runLater);
walletServiceObservable.subscribe(
next -> {
//log.trace("wallet next");
},
error -> Platform.runLater(() -> {
log.trace("wallet error");
setWalletServiceException(error);
}),
() -> {
log.trace("wallet completed");
});
Observable<UpdateProcess.State> updateProcessObservable = this.updateProcess.getProcess();
updateProcessObservable.subscribe(next -> {
//log.trace("updateProcess next");
},
error -> {
log.trace("updateProcess error");
},
() -> {
log.trace("updateProcess completed");
});
Observable<?> allServices = Observable.merge(bootstrapStateAsObservable, walletServiceObservable, updateProcessObservable);
allServices.subscribe(
next -> {
},
error -> log.error(error.toString()),
() -> Platform.runLater(this::onAllServicesInitialized)
);
}
private void onAllServicesInitialized() {
log.trace("backend completed");
tradeManager.getPendingTrades().addListener((ListChangeListener<Trade>) change -> updateNumPendingTrades());
updateNumPendingTrades();
showAppScreen.set(true);
// For alpha version
if (!user.isRegistered()) {
FiatAccount fiatAccount = new FiatAccount(FiatAccount.Type.IRC,
"EUR",
CountryUtil.getDefaultCountry(),
"Demo (Name of bank)",
"Demo (Account holder name)",
"Demo (E.g. IBAN) ",
"Demo (E.g. BIC) ");
user.addFiatAccount(fiatAccount);
user.setAccountID(walletService.getRegistrationAddressEntry().toString());
}
// Load all arbitrators in background. Any class requiring a loaded list of arbitrators need to register itself as listener to handle the async
// operation.
log.debug("loadAllArbitrators");
arbitrationRepository.loadAllArbitrators();
tradeManager.onAllServicesInitialized();
}
private void applyUpdateState(UpdateProcess.State state) {
switch (state) {
case CHECK_FOR_UPDATES:
updateInfo.set("Checking for updates...");
updateIconId.set("image-update-in-progress");
break;
case UPDATE_AVAILABLE:
updateInfo.set("New update available. Please restart!");
updateIconId.set("image-update-available");
showRestartButton.set(true);
break;
case UP_TO_DATE:
updateInfo.set("Software is up to date.");
updateIconId.set("image-update-up-to-date");
break;
case FAILURE:
updateInfo.set(updateProcess.getErrorMessage());
updateIconId.set("image-update-failed");
break;
}
}
private void setBootstrapState(BootstrapState state) {
switch (state) {
case DISCOVERY_DIRECT_SUCCEEDED:
bootstrapIconId.set("image-connection-direct");
break;
case DISCOVERY_MANUAL_PORT_FORWARDING_SUCCEEDED:
case DISCOVERY_AUTO_PORT_FORWARDING_SUCCEEDED:
bootstrapIconId.set("image-connection-nat");
break;
case RELAY_SUCCEEDED:
bootstrapIconId.set("image-connection-relay");
break;
default:
bootstrapIconId.set(null);
break;
}
switch (state) {
case DISCOVERY_DIRECT_SUCCEEDED:
case DISCOVERY_MANUAL_PORT_FORWARDING_SUCCEEDED:
case DISCOVERY_AUTO_PORT_FORWARDING_SUCCEEDED:
case RELAY_SUCCEEDED:
bootstrapInfo.set("Successfully connected to P2P network: " + state.getMessage());
bootstrapProgress.set(1);
break;
default:
bootstrapInfo.set("Connecting to the Bitsquare network: " + state.getMessage());
bootstrapProgress.set(-1);
break;
}
}
private void setWalletServiceException(Throwable error) {
setBitcoinNetworkSyncProgress(0);
blockchainSyncInfo.set("Connecting to the bitcoin network failed.");
if (error instanceof TimeoutException) {
walletServiceErrorMsg.set("Please check your network connection.\n\n" +
"You must allow outgoing TCP connections to port 18333 for the bitcoin testnet.\n\n" +
"See https://github.com/bitsquare/bitsquare/wiki for instructions.");
}
else if (error.getMessage() != null) {
walletServiceErrorMsg.set(error.getMessage());
}
else {
walletServiceErrorMsg.set(error.toString());
}
}
public StringConverter<FiatAccount> getBankAccountsConverter() {
return new StringConverter<FiatAccount>() {
@Override
public String toString(FiatAccount fiatAccount) {
return fiatAccount.nameOfBank;
}
@Override
public FiatAccount fromString(String s) {
return null;
}
};
}
public ObservableList<FiatAccount> getBankAccounts() {
return user.fiatAccountsObservableList();
}
public void setCurrentBankAccount(FiatAccount currentFiatAccount) {
user.setCurrentFiatAccountProperty(currentFiatAccount);
}
private void updateNumPendingTrades() {
long numPendingTrades = tradeManager.getPendingTrades().size();
if (numPendingTrades > 0)
numPendingTradesAsString.set(String.valueOf(numPendingTrades));
if (numPendingTrades > 9)
numPendingTradesAsString.set("*");
showPendingTradesNotification.set(numPendingTrades > 0);
}
private void setBitcoinNetworkSyncProgress(double value) {
blockchainSyncProgress.set(value);
if (value >= 1) {
blockchainSyncInfo.set("Blockchain synchronization complete.");
blockchainSyncIconId.set("image-connection-synced");
}
else if (value > 0.0) {
blockchainSyncInfo.set("Synchronizing blockchain: " + formatter.formatToPercent(value));
}
else {
blockchainSyncInfo.set("Connecting to the bitcoin network...");
}
}
}

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<TabPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.AccountView"
prefHeight="630.0" prefWidth="1000.0"
AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0"
AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0"
xmlns:fx="http://javafx.com/fxml">
<Tab fx:id="accountSettingsTab" closable="false"/>
<Tab fx:id="arbitratorSettingsTab" text="Arbitrator setup" closable="false"/>
</TabPane>

View file

@ -0,0 +1,126 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account;
import io.bitsquare.common.view.ActivatableView;
import io.bitsquare.common.view.CachingViewLoader;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.View;
import io.bitsquare.common.view.ViewLoader;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.account.arbitrator.ArbitratorSettingsView;
import io.bitsquare.gui.main.account.settings.AccountSettingsView;
import io.bitsquare.gui.main.account.setup.AccountSetupWizard;
import javax.inject.Inject;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.scene.control.*;
@FxmlView
public class AccountView extends ActivatableView<TabPane, AccountViewModel> {
@FXML Tab accountSettingsTab, arbitratorSettingsTab;
private Navigation.Listener navigationListener;
private ChangeListener<Tab> tabChangeListener;
private final ViewLoader viewLoader;
private final Navigation navigation;
private View accountSetupWizardView;
private Tab tab;
@Inject
private AccountView(AccountViewModel model, CachingViewLoader viewLoader, Navigation navigation) {
super(model);
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
navigationListener = viewPath -> {
if (viewPath.size() == 3 && viewPath.indexOf(AccountView.class) == 1)
loadView(viewPath.tip());
};
tabChangeListener = (ov, oldValue, newValue) -> {
if (newValue == accountSettingsTab)
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class);
else
navigation.navigateTo(MainView.class, AccountView.class, ArbitratorSettingsView.class);
};
}
@Override
public void activate() {
navigation.addListener(navigationListener);
root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener);
if (navigation.getCurrentPath().size() == 2 && navigation.getCurrentPath().get(1) == AccountView.class) {
if (model.getNeedRegistration()) {
navigation.navigateTo(MainView.class, AccountView.class, AccountSetupWizard.class);
}
else {
if (root.getSelectionModel().getSelectedItem() == accountSettingsTab)
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class);
else
navigation.navigateTo(MainView.class, AccountView.class, ArbitratorSettingsView.class);
}
}
}
@Override
public void deactivate() {
navigation.removeListener(navigationListener);
root.getSelectionModel().selectedItemProperty().removeListener(tabChangeListener);
if (accountSetupWizardView != null)
tab.setContent(null);
}
private void loadView(Class<? extends View> viewClass) {
View view = viewLoader.load(viewClass);
if (view instanceof AccountSettingsView) {
tab = accountSettingsTab;
tab.setText("Account settings");
arbitratorSettingsTab.setDisable(false);
}
else if (view instanceof AccountSetupWizard) {
tab = accountSettingsTab;
tab.setText("Account setup");
arbitratorSettingsTab.setDisable(true);
accountSetupWizardView = view;
}
else if (view instanceof ArbitratorSettingsView) {
tab = arbitratorSettingsTab;
}
else {
throw new IllegalArgumentException("View not supported: " + view);
}
// for IRC demo we deactivate the arbitratorSettingsTab
// arbitratorSettingsTab.setDisable(true);
tab.setContent(view.getRoot());
root.getSelectionModel().select(tab);
}
}

View file

@ -0,0 +1,37 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account;
import io.bitsquare.common.model.ViewModel;
import io.bitsquare.user.User;
import com.google.inject.Inject;
class AccountViewModel implements ViewModel {
private final User user;
@Inject
public AccountViewModel(User user) {
this.user = user;
}
boolean getNeedRegistration() {
return user.getAccountId() == null;
}
}

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.arbitrator.ArbitratorSettingsView"
prefHeight="660.0" prefWidth="1000.0"
xmlns:fx="http://javafx.com/fxml">
<VBox spacing="20">
<padding>
<Insets left="20" top="20" right="20"/>
</padding>
<Label text="Arbitration system is not implemented yet. At the moment there are just very basic UI screens for the registration and editing."/>
<Button text="Register yourself as an arbitrator" onAction="#onArbitratorRegistration"/>
<Button text="Edit my arbitrator details" onAction="#onArbitratorEdit"/>
</VBox>
</AnchorPane>

View file

@ -0,0 +1,80 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.arbitrator;
import io.bitsquare.common.view.AbstractView;
import io.bitsquare.common.view.CachingViewLoader;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.View;
import io.bitsquare.common.view.ViewLoader;
import io.bitsquare.gui.main.account.arbitrator.registration.ArbitratorRegistrationView;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.stage.Modality;
import javafx.stage.Stage;
@FxmlView
public class ArbitratorSettingsView extends AbstractView {
private final ViewLoader viewLoader;
private final Stage primaryStage;
@Inject
private ArbitratorSettingsView(CachingViewLoader viewLoader, Stage primaryStage) {
this.viewLoader = viewLoader;
this.primaryStage = primaryStage;
}
@FXML
public void onArbitratorRegistration() {
View view = viewLoader.load(ArbitratorRegistrationView.class);
showStage(view);
}
@FXML
public void onArbitratorEdit() {
View view = viewLoader.load(ArbitratorRegistrationView.class);
showStage(view);
((ArbitratorRegistrationView) view).setEditMode(true);
}
private void showStage(View view) {
Stage stage = new Stage();
stage.setTitle("Arbitrator");
stage.setMinWidth(800);
stage.setMinHeight(400);
stage.setWidth(800);
stage.setHeight(600);
stage.setX(primaryStage.getX() + 50);
stage.setY(primaryStage.getY() + 50);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(primaryStage);
Scene scene = new Scene((Parent) view.getRoot(), 800, 600);
stage.setScene(scene);
stage.setOnCloseRequest(e -> {
// need to unset root to be re-uasabel to other popups screens
scene.setRoot(new Pane());
});
stage.show();
}
}

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.arbitrator.browser.BrowserView"
prefHeight="600" prefWidth="800" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="10.0"
AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0"
xmlns:fx="http://javafx.com/fxml">
<Pane fx:id="arbitratorProfile"/>
<Button fx:id="prevButton" text="Previous" onAction="#onPrevious" AnchorPane.bottomAnchor="45.0"
AnchorPane.leftAnchor="10.0"/>
<Button fx:id="nextButton" text="Next" onAction="#onNext" AnchorPane.bottomAnchor="45.0"
AnchorPane.rightAnchor="10.0"/>
<Button fx:id="selectButton" text="Select" onAction="#onSelect" AnchorPane.bottomAnchor="45.0"
AnchorPane.leftAnchor="350.0"/>
<Button fx:id="closeButton" text="close" onAction="#onClose" AnchorPane.bottomAnchor="10.0"
AnchorPane.leftAnchor="10.0"/>
</AnchorPane>

View file

@ -0,0 +1,140 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.arbitrator.browser;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.arbitration.ArbitratorService;
import io.bitsquare.common.view.ActivatableView;
import io.bitsquare.common.view.CachingViewLoader;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.View;
import io.bitsquare.common.view.ViewLoader;
import io.bitsquare.gui.main.account.arbitrator.profile.ArbitratorProfileView;
import io.bitsquare.user.AccountSettings;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
@FxmlView
public class BrowserView extends ActivatableView<Pane, Void> implements ArbitratorService.Listener {
@FXML Button prevButton, nextButton, selectButton, closeButton;
@FXML Pane arbitratorProfile;
private Arbitrator currentArbitrator;
private ArbitratorProfileView arbitratorProfileView;
private int index = -1;
private final List<Arbitrator> allArbitrators = new ArrayList<>();
private final ViewLoader viewLoader;
private final AccountSettings accountSettings;
private final ArbitratorService arbitratorService;
@Inject
public BrowserView(CachingViewLoader viewLoader, AccountSettings accountSettings,
ArbitratorService arbitratorService) {
this.viewLoader = viewLoader;
this.accountSettings = accountSettings;
this.arbitratorService = arbitratorService;
}
@Override
public void initialize() {
arbitratorService.addListener(this);
/* arbitratorService.loadAllArbitrators(() -> log.debug("Arbitrators successful loaded " + arbitratorService.getAllArbitrators().size()),
(errorMessage -> log.error(errorMessage)));*/
View view = viewLoader.load(ArbitratorProfileView.class);
root.getChildren().set(0, view.getRoot());
arbitratorProfileView = (ArbitratorProfileView) view;
checkButtonState();
}
@Override
public void onArbitratorAdded(Arbitrator arbitrator) {
}
@Override
public void onAllArbitratorsLoaded(Map<String, Arbitrator> arbitratorsMap) {
}
/*
@Override
public void onAllArbitratorsLoaded(List<Arbitrator> arbitrators) {
allArbitrators.clear();
allArbitrators.addAll(arbitrators);
if (!allArbitrators.isEmpty()) {
index = 0;
currentArbitrator = allArbitrators.get(index);
arbitratorProfileView.applyArbitrator(currentArbitrator);
checkButtonState();
}
}*/
@Override
public void onArbitratorRemoved(Arbitrator arbitrator) {
}
@FXML
public void onPrevious() {
if (index > 0) {
index--;
currentArbitrator = allArbitrators.get(index);
arbitratorProfileView.applyArbitrator(currentArbitrator);
}
checkButtonState();
}
@FXML
public void onNext() {
if (index < allArbitrators.size() - 1) {
index++;
currentArbitrator = allArbitrators.get(index);
arbitratorProfileView.applyArbitrator(currentArbitrator);
}
checkButtonState();
}
@FXML
public void onSelect() {
accountSettings.addAcceptedArbitrator(currentArbitrator);
}
@FXML
public void onClose() {
Stage stage = (Stage) root.getScene().getWindow();
stage.close();
}
private void checkButtonState() {
prevButton.setDisable(index < 1);
nextButton.setDisable(index == allArbitrators.size() - 1 || index == -1);
}
}

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.arbitrator.profile.ArbitratorProfileView"
hgap="5.0" vgap="5.0" AnchorPane.bottomAnchor="80.0" AnchorPane.leftAnchor="10.0"
AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0"
xmlns:fx="http://javafx.com/fxml">
<Label fx:id="nameLabel" text="Name:"/>
<TextField fx:id="nameTextField" editable="false" focusTraversable="false" GridPane.columnIndex="1"/>
<Label text="Languages:" GridPane.rowIndex="1"/>
<TextField fx:id="languagesTextField" editable="false" focusTraversable="false" GridPane.columnIndex="1"
GridPane.rowIndex="1"/>
<Label text="Reputation:" GridPane.rowIndex="2"/>
<TextField fx:id="reputationTextField" editable="false" focusTraversable="false" GridPane.columnIndex="1"
GridPane.rowIndex="2"/>
<Label text="Max. trade volume:" GridPane.rowIndex="3"/>
<TextField fx:id="maxTradeVolumeTextField" editable="false" focusTraversable="false" GridPane.columnIndex="1"
GridPane.rowIndex="3"/>
<Label text="Passive service fee:" GridPane.rowIndex="4"/>
<TextField fx:id="passiveServiceFeeTextField" editable="false" focusTraversable="false" GridPane.columnIndex="1"
GridPane.rowIndex="4"/>
<Label text="Arbitration fee:" GridPane.rowIndex="5"/>
<TextField fx:id="feeTextField" editable="false" focusTraversable="false" GridPane.columnIndex="1"
GridPane.columnSpan="2" GridPane.rowIndex="5"/>
<Label text="Methods of arbitration:" GridPane.rowIndex="6"/>
<TextField fx:id="methodsTextField" editable="false" focusTraversable="false" GridPane.columnIndex="1"
GridPane.columnSpan="2" GridPane.rowIndex="6"/>
<Label text="ID verifications:" GridPane.rowIndex="7"/>
<TextField fx:id="idVerificationsTextField" editable="false" focusTraversable="false" GridPane.columnIndex="1"
GridPane.columnSpan="2" GridPane.rowIndex="7"/>
<Label text="Web page:" GridPane.rowIndex="9"/>
<TextField fx:id="webPageTextField" editable="false" focusTraversable="false" GridPane.columnIndex="1"
GridPane.columnSpan="2" GridPane.rowIndex="9"/>
<Label text="Description:" GridPane.rowIndex="10" GridPane.valignment="TOP">
<GridPane.margin>
<Insets top="10.0"/>
</GridPane.margin>
</Label>
<TextArea fx:id="descriptionTextArea" editable="false" focusTraversable="false" prefHeight="150.0"
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="10"/>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" minWidth="10.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="ALWAYS"/>
</rowConstraints>
</GridPane>

View file

@ -0,0 +1,73 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.arbitrator.profile;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.common.view.AbstractView;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.gui.util.BSFormatter;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.*;
@FxmlView
public class ArbitratorProfileView extends AbstractView {
@FXML Label nameLabel;
@FXML TextArea descriptionTextArea;
@FXML TextField nameTextField, languagesTextField, reputationTextField, feeTextField, methodsTextField,
passiveServiceFeeTextField, idVerificationsTextField, webPageTextField, maxTradeVolumeTextField;
private final BSFormatter formatter;
@Inject
public ArbitratorProfileView(BSFormatter formatter) {
this.formatter = formatter;
}
public void applyArbitrator(Arbitrator arbitrator) {
if (arbitrator != null && arbitrator.getIdType() != null) {
String name = "";
switch (arbitrator.getIdType()) {
case REAL_LIFE_ID:
name = "Name:";
break;
case NICKNAME:
name = "Nickname:";
break;
case COMPANY:
name = "Company:";
break;
}
nameLabel.setText(name);
nameTextField.setText(arbitrator.getName());
// languagesTextField.setText(formatter.languageLocalesToString(arbitrator.getLanguages()));
reputationTextField.setText(arbitrator.getReputation().toString());
feeTextField.setText(String.valueOf(arbitrator.getFee() + " BTC"));
methodsTextField.setText(formatter.arbitrationMethodsToString(arbitrator.getArbitrationMethods()));
idVerificationsTextField.setText(
formatter.arbitrationIDVerificationsToString(arbitrator.getIdVerifications()));
webPageTextField.setText(arbitrator.getWebUrl());
descriptionTextArea.setText(arbitrator.getDescription());
}
}
}

View file

@ -0,0 +1,192 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="root"
fx:controller="io.bitsquare.gui.main.account.arbitrator.registration.ArbitratorRegistrationView"
prefHeight="600" prefWidth="800" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="10.0"
AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0"
xmlns:fx="http://javafx.com/fxml">
<Accordion fx:id="accordion" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="10.0"
AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
<panes>
<TitledPane fx:id="profileTitledPane" text="Profile" expanded="true">
<GridPane hgap="5.0" vgap="5.0" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0"
AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
<Label fx:id="nameLabel" text="Name:"/>
<TextField fx:id="nameTextField" GridPane.columnIndex="1" GridPane.columnSpan="3"/>
<Label text="ID type:" GridPane.rowIndex="1"/>
<TextField fx:id="idTypeTextField" editable="false" focusTraversable="false"
GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<ComboBox fx:id="idTypeComboBox" onAction="#onSelectIDType" promptText="Select ID type"
prefWidth="150.0" GridPane.columnIndex="3" GridPane.rowIndex="1"/>
<Label text="Supported language(s):" GridPane.rowIndex="2"/>
<TextField fx:id="languagesTextField" editable="false" focusTraversable="false"
GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<Button onAction="#onClearLanguages" text="Clear" GridPane.columnIndex="2" GridPane.rowIndex="2"/>
<ComboBox fx:id="languageComboBox" onAction="#onAddLanguage" prefWidth="150.0"
promptText="Add language" GridPane.columnIndex="3" GridPane.rowIndex="2"/>
<Label text="Max. trade volume:" GridPane.rowIndex="3"/>
<TextField fx:id="maxTradeVolumeTextField" promptText="Recommended: 0.1 - 2 BTC"
GridPane.columnIndex="1" GridPane.rowIndex="3" GridPane.columnSpan="3"/>
<Label text="Passive service fee (%):" GridPane.rowIndex="4"/>
<TextField fx:id="passiveServiceFeeTextField" promptText="Recommended: 0.1 - 1%"
GridPane.columnIndex="1" GridPane.rowIndex="4" GridPane.columnSpan="3"/>
<Label text="Min. passive service fee" GridPane.rowIndex="5"/>
<TextField fx:id="minPassiveServiceFeeTextField" promptText="Recommended: 0.0001 - 0.005 BTC"
GridPane.columnIndex="1" GridPane.rowIndex="5" GridPane.columnSpan="3"/>
<Label text="Arbitration fee (%):" GridPane.rowIndex="6"/>
<TextField fx:id="arbitrationFeeTextField" promptText="Recommended: 5 - 20 %"
GridPane.columnIndex="1" GridPane.rowIndex="6" GridPane.columnSpan="3"/>
<Label text="Min. arbitration fee:" GridPane.rowIndex="7"/>
<TextField fx:id="minArbitrationFeeTextField" promptText="Recommended: 0.1 - 0.2 BTC"
GridPane.columnIndex="1" GridPane.rowIndex="7" GridPane.columnSpan="3"/>
<Label text="Method(s) used for arbitration:" GridPane.rowIndex="8"/>
<TextField fx:id="methodsTextField" editable="false" focusTraversable="false"
GridPane.columnIndex="1" GridPane.rowIndex="8"/>
<Button onAction="#onClearMethods" text="Clear" GridPane.columnIndex="2" GridPane.rowIndex="8"/>
<ComboBox fx:id="methodsComboBox" onAction="#onAddMethod" prefWidth="150.0" promptText="Add method"
GridPane.columnIndex="3" GridPane.rowIndex="8"/>
<Label text="Offer ID verification(s):" GridPane.rowIndex="9"/>
<TextField fx:id="idVerificationsTextField" promptText="Optional" editable="false"
focusTraversable="false" GridPane.columnIndex="1" GridPane.rowIndex="9"/>
<Button onAction="#onClearIDVerifications" text="Clear" GridPane.columnIndex="2"
GridPane.rowIndex="9"/>
<ComboBox fx:id="idVerificationsComboBox" onAction="#onAddIDVerification" prefWidth="150.0"
promptText="Add verification" GridPane.columnIndex="3"
GridPane.rowIndex="9"/>
<Label text="Web page:" GridPane.rowIndex="10"/>
<TextField fx:id="webPageTextField" GridPane.columnIndex="1" GridPane.columnSpan="3"
GridPane.rowIndex="10"/>
<Label text="Description:" GridPane.rowIndex="11" GridPane.valignment="TOP">
<GridPane.margin>
<Insets top="10.0"/>
</GridPane.margin>
</Label>
<TextArea fx:id="descriptionTextArea" prefHeight="150.0" GridPane.columnIndex="1"
GridPane.columnSpan="3" GridPane.rowIndex="11"/>
<Button fx:id="saveProfileButton" defaultButton="true" onAction="#onSaveProfile" text="Next"
GridPane.columnIndex="1" GridPane.rowIndex="12"/>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" minWidth="10.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="150.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="ALWAYS"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES"/>
</rowConstraints>
</GridPane>
</TitledPane>
<TitledPane fx:id="paySecurityDepositTitledPane" text="Pay security deposit">
<AnchorPane>
<VBox spacing="20" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<Label fx:id="infoLabel"/>
<GridPane hgap="5.0" vgap="5.0">
<Label text="Security deposit address:"/>
<TextField fx:id="securityDepositAddressTextField" editable="false" focusTraversable="false"
GridPane.columnIndex="1"/>
<Label fx:id="copyIcon" id="copy-icon" GridPane.columnIndex="2">
<padding>
<Insets bottom="0.0" left="0.0" right="0.0" top="-1.0"/>
</padding>
<tooltip>
<Tooltip text="Copy address to clipboard"/>
</tooltip>
</Label>
<Label text="Balance:" GridPane.rowIndex="1"/>
<TextField fx:id="balanceTextField" editable="false" focusTraversable="false"
GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<ConfidenceProgressIndicator fx:id="progressIndicator" GridPane.columnIndex="2"
GridPane.halignment="LEFT"
GridPane.rowIndex="1" GridPane.rowSpan="2"
GridPane.valignment="TOP">
<GridPane.margin>
<Insets top="2.0"/>
</GridPane.margin>
</ConfidenceProgressIndicator>
<Label fx:id="confirmationLabel" text="Checking confirmations..." GridPane.columnIndex="3"
GridPane.rowIndex="1"/>
<Button fx:id="paymentDoneButton" defaultButton="true" onAction="#onPaymentDone"
text="Payment done" disable="true" GridPane.columnIndex="1"
GridPane.rowIndex="2"/>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES"/>
<ColumnConstraints hgrow="ALWAYS"/>
<ColumnConstraints hgrow="SOMETIMES" prefWidth="20.0" minWidth="20"/>
<ColumnConstraints hgrow="SOMETIMES"/>
<ColumnConstraints hgrow="SOMETIMES"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
</rowConstraints>
</GridPane>
</VBox>
</AnchorPane>
</TitledPane>
</panes>
</Accordion>
</AnchorPane>

View file

@ -0,0 +1,366 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.arbitrator.registration;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.arbitration.ArbitratorService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.view.ActivatableView;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.BSResources;
import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.util.Utilities;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.Wallet;
import org.bitcoinj.core.WalletEventListener;
import org.bitcoinj.script.Script;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import javax.inject.Inject;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
@FxmlView
public class ArbitratorRegistrationView extends ActivatableView<AnchorPane, Void> {
@FXML Accordion accordion;
@FXML TextArea descriptionTextArea;
@FXML Button saveProfileButton, paymentDoneButton;
@FXML Label nameLabel, infoLabel, copyIcon, confirmationLabel;
@FXML ComboBox<String> languageComboBox;
@FXML ComboBox<Arbitrator.ID_TYPE> idTypeComboBox;
@FXML ComboBox<Arbitrator.METHOD> methodsComboBox;
@FXML ConfidenceProgressIndicator progressIndicator;
@FXML ComboBox<Arbitrator.ID_VERIFICATION> idVerificationsComboBox;
@FXML TitledPane profileTitledPane, paySecurityDepositTitledPane;
@FXML TextField nameTextField, idTypeTextField, languagesTextField, maxTradeVolumeTextField,
passiveServiceFeeTextField, minPassiveServiceFeeTextField, arbitrationFeeTextField,
minArbitrationFeeTextField, methodsTextField, idVerificationsTextField, webPageTextField,
securityDepositAddressTextField, balanceTextField;
private boolean isEditMode;
private Arbitrator.ID_TYPE idType;
private List<String> languageCodes = new ArrayList<>();
private List<Arbitrator.METHOD> methodList = new ArrayList<>();
private List<Arbitrator.ID_VERIFICATION> idVerificationList = new ArrayList<>();
// TODO not set
private Arbitrator arbitrator;
private final WalletService walletService;
private final ArbitratorService arbitratorService;
private final BSFormatter formatter;
@Inject
private ArbitratorRegistrationView(WalletService walletService,
ArbitratorService arbitratorService, BSFormatter formatter) {
this.walletService = walletService;
this.arbitratorService = arbitratorService;
this.formatter = formatter;
}
@Override
public void initialize() {
accordion.setExpandedPane(profileTitledPane);
applyArbitrator();
languageComboBox.setItems(FXCollections.observableArrayList(LanguageUtil.getAllLanguageLocaleCodes()));
languageComboBox.setConverter(new StringConverter<String>() {
@Override
public String toString(String code) {
return LanguageUtil.getDisplayName(code);
}
@Override
public String fromString(String s) {
return null;
}
});
idTypeComboBox.setItems(FXCollections.observableArrayList(
new ArrayList<>(EnumSet.allOf(Arbitrator.ID_TYPE.class))));
idTypeComboBox.setConverter(new StringConverter<Arbitrator.ID_TYPE>() {
@Override
public String toString(Arbitrator.ID_TYPE item) {
return BSResources.get(item.toString());
}
@Override
public Arbitrator.ID_TYPE fromString(String s) {
return null;
}
});
methodsComboBox.setItems(FXCollections.observableArrayList(new ArrayList<>(EnumSet.allOf(Arbitrator.METHOD
.class))));
methodsComboBox.setConverter(new StringConverter<Arbitrator.METHOD>() {
@Override
public String toString(Arbitrator.METHOD item) {
return BSResources.get(item.toString());
}
@Override
public Arbitrator.METHOD fromString(String s) {
return null;
}
});
idVerificationsComboBox.setItems(
FXCollections.observableArrayList(new ArrayList<>(EnumSet.allOf(Arbitrator.ID_VERIFICATION.class))));
idVerificationsComboBox.setConverter(new StringConverter<Arbitrator.ID_VERIFICATION>() {
@Override
public String toString(Arbitrator.ID_VERIFICATION item) {
return BSResources.get(item.toString());
}
@Override
public Arbitrator.ID_VERIFICATION fromString(String s) {
return null;
}
});
}
public void setEditMode(boolean isEditMode) {
this.isEditMode = isEditMode;
if (isEditMode) {
saveProfileButton.setText("Save");
profileTitledPane.setCollapsible(false);
paySecurityDepositTitledPane.setVisible(false);
}
}
@FXML
public void onSelectIDType() {
idType = idTypeComboBox.getSelectionModel().getSelectedItem();
if (idType != null) {
idTypeTextField.setText(BSResources.get(idType.toString()));
String name = "";
switch (idType) {
case REAL_LIFE_ID:
name = "Name:";
break;
case NICKNAME:
name = "Nickname:";
break;
case COMPANY:
name = "Company:";
break;
}
nameLabel.setText(name);
idTypeComboBox.getSelectionModel().clearSelection();
}
}
@FXML
public void onAddLanguage() {
String item = languageComboBox.getSelectionModel().getSelectedItem();
if (!languageCodes.contains(item) && item != null) {
languageCodes.add(item);
languagesTextField.setText(formatter.languageCodesToString(languageCodes));
languageComboBox.getSelectionModel().clearSelection();
}
}
@FXML
public void onClearLanguages() {
languageCodes.clear();
languagesTextField.setText("");
}
@FXML
public void onAddMethod() {
Arbitrator.METHOD item = methodsComboBox.getSelectionModel().getSelectedItem();
if (!methodList.contains(item) && item != null) {
methodList.add(item);
methodsTextField.setText(formatter.arbitrationMethodsToString(methodList));
methodsComboBox.getSelectionModel().clearSelection();
}
}
@FXML
public void onClearMethods() {
methodList.clear();
methodsTextField.setText("");
}
@FXML
public void onAddIDVerification() {
Arbitrator.ID_VERIFICATION idVerification = idVerificationsComboBox.getSelectionModel().getSelectedItem();
if (idVerification != null) {
if (!idVerificationList.contains(idVerification)) {
idVerificationList.add(idVerification);
idVerificationsTextField.setText(
formatter.arbitrationIDVerificationsToString(idVerificationList));
}
}
idVerificationsComboBox.getSelectionModel().clearSelection();
}
@FXML
public void onClearIDVerifications() {
idVerificationList.clear();
idVerificationsTextField.setText("");
}
@FXML
public void onSaveProfile() {
arbitrator.setWebUrl(webPageTextField.getText());
arbitrator.setFee(formatter.parseToCoin(arbitrationFeeTextField.getText()));
arbitrator.setIdType(idType);
arbitrator.setIdVerifications(idVerificationList);
arbitrator.setLanguageCodes(languageCodes);
arbitrator.setArbitrationMethods(methodList);
arbitrator.save();
if (isEditMode) {
close();
}
else {
setupPaySecurityDepositScreen();
accordion.setExpandedPane(paySecurityDepositTitledPane);
}
arbitratorService.addArbitrator(arbitrator,
() -> {
// log.debug("arbitrator added successfully " + arbitratorService.getAllArbitrators().size());
},
log::error);
}
@FXML
public void onPaymentDone() {
}
private void setupPaySecurityDepositScreen() {
infoLabel.setText("You need to pay 2 x the max. trading volume as security deposit.\n\nThat payment will be " +
"locked into a MultiSig fund and be refunded when you leave the arbitration pool.\nIn case of fraud " +
"(collusion, not fulfilling the min. dispute quality requirements) you will lose your security " +
"deposit.\n" +
"If you have a negative feedback from your clients you will lose a part of the security deposit,\n" +
"depending on the overall relation of negative to positive ratings you received after a dispute " +
"resolution.\n\nPlease pay in 2 BTC");
String securityDepositAddress = walletService.getRegistrationAddressEntry() != null ?
walletService.getRegistrationAddressEntry().toString() : "";
securityDepositAddressTextField.setText(securityDepositAddress);
AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY);
copyIcon.setOnMouseClicked(e -> Utilities.copyToClipboard(securityDepositAddress));
paymentDoneButton.setDisable(walletService.getArbitratorDepositBalance().isZero());
log.debug("getArbitratorDepositBalance " + walletService.getArbitratorDepositBalance());
walletService.getWallet().addEventListener(new WalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
paymentDoneButton.setDisable(newBalance.isZero());
}
@Override
public void onCoinsSent(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
}
@Override
public void onReorganize(Wallet wallet) {
}
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
}
@Override
public void onWalletChanged(Wallet wallet) {
}
@Override
public void onScriptsChanged(Wallet var1, List<Script> scripts, boolean isAddingScripts) {
}
@Override
public void onKeysAdded(List<ECKey> keys) {
}
});
}
private void applyArbitrator() {
if (arbitrator != null) {
String name = "";
switch (arbitrator.getIdType()) {
case REAL_LIFE_ID:
name = "Name:";
break;
case NICKNAME:
name = "Nickname:";
break;
case COMPANY:
name = "Company:";
break;
}
nameLabel.setText(name);
nameTextField.setText(arbitrator.getName());
idTypeTextField.setText(BSResources.get(arbitrator.getIdType().toString()));
// languagesTextField.setText(formatter.languageLocalesToString(arbitrator.getLanguages()));
arbitrationFeeTextField.setText(String.valueOf(arbitrator.getFee()));
methodsTextField.setText(formatter.arbitrationMethodsToString(arbitrator.getArbitrationMethods()));
idVerificationsTextField.setText(
formatter.arbitrationIDVerificationsToString(arbitrator.getIdVerifications()));
webPageTextField.setText(arbitrator.getWebUrl());
descriptionTextArea.setText(arbitrator.getDescription());
idType = arbitrator.getIdType();
languageCodes = arbitrator.getLanguageCodes();
methodList = arbitrator.getArbitrationMethods();
idVerificationList = arbitrator.getIdVerifications();
}
}
private void close() {
Stage stage = (Stage) root.getScene().getWindow();
stage.close();
}
}

View file

@ -0,0 +1,92 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.altcoin;
import io.bitsquare.common.model.Activatable;
import io.bitsquare.common.model.DataModel;
import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.locale.CountryUtil;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.user.User;
import com.google.inject.Inject;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
class AltCoinAccountDataModel implements Activatable, DataModel {
private final User user;
final StringProperty nickName = new SimpleStringProperty();
final StringProperty currencyCode = new SimpleStringProperty();
final ObjectProperty<FiatAccount.Type> type = new SimpleObjectProperty<>();
final ObservableList<FiatAccount.Type> allTypes =
FXCollections.observableArrayList(FiatAccount.Type.getAllBankAccountTypes());
final ObservableList<String> allCurrencyCodes = FXCollections.observableArrayList(CurrencyUtil.getAllCurrencyCodes());
final ObservableList<FiatAccount> allFiatAccounts = FXCollections.observableArrayList();
@Inject
public AltCoinAccountDataModel(User user) {
this.user = user;
}
@Override
public void activate() {
allFiatAccounts.setAll(user.fiatAccountsObservableList());
}
@Override
public void deactivate() {
// no-op
}
void saveBankAccount() {
FiatAccount fiatAccount = new FiatAccount(type.get(),
currencyCode.get(),
CountryUtil.getDefaultCountry(),
nickName.get(),
nickName.get(),
"irc",
"irc");
user.addFiatAccount(fiatAccount);
allFiatAccounts.setAll(user.fiatAccountsObservableList());
reset();
}
void setType(FiatAccount.Type type) {
this.type.set(type);
}
void setCurrencyCode(String currencyCode) {
this.currencyCode.set(currencyCode);
}
private void reset() {
nickName.set(null);
type.set(null);
currencyCode.set(null);
}
}

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import io.bitsquare.gui.components.InfoDisplay?>
<?import io.bitsquare.gui.components.InputTextField?>
<?import io.bitsquare.gui.components.TitledGroupBg?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.content.altcoin.AltCoinAccountView" hgap="5.0"
vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<!--
Setup
-->
<TitledGroupBg text="Setup your payments account" GridPane.rowSpan="8"/>
<Label text="Payments method:" GridPane.rowIndex="0">
<GridPane.margin>
<Insets top="10"/>
</GridPane.margin>
</Label>
<ComboBox fx:id="typesComboBox" promptText="Select payments method" onAction="#onSelectType"
GridPane.rowIndex="0" GridPane.columnIndex="1">
<GridPane.margin>
<Insets top="10"/>
</GridPane.margin>
</ComboBox>
<Label text="Check if your nickname is available:" GridPane.rowIndex="1"/>
<Hyperlink text="Open IRC @Freenode #bitsquare-trading in browser"
onAction="#onOpenIRC" GridPane.columnIndex="1" GridPane.rowIndex="1" minHeight="26"/>
<Label text="Your IRC nick name:" GridPane.rowIndex="2"/>
<InputTextField fx:id="ircNickNameTextField" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<Label text="Currency:" GridPane.rowIndex="3"/>
<ComboBox fx:id="currencyComboBox" promptText="Select currency"
onAction="#onSelectCurrency" GridPane.columnIndex="1"
GridPane.rowIndex="3"/>
<InfoDisplay gridPane="$root" onAction="#onOpenSetupHelp" rowIndex="4"
text="The payments account data will be saved in a encrypted form to the Bitcoin block chain and will be used in the trade process for account verification."/>
<HBox fx:id="buttonsHBox" GridPane.columnIndex="1" GridPane.rowIndex="5" spacing="10">
<Button fx:id="saveButton" text="Save" onAction="#onSave" defaultButton="true" disable="true"/>
<GridPane.margin>
<Insets top="15.0" bottom="5.0"/>
</GridPane.margin>
</HBox>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="250.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
</rowConstraints>
</GridPane>

View file

@ -0,0 +1,196 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.altcoin;
import io.bitsquare.common.view.ActivatableViewAndModel;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.Wizard;
import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.main.help.Help;
import io.bitsquare.gui.main.help.HelpId;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.util.Utilities;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.util.Callback;
/**
* Just temporary for giving the user a possibility to test the app via simulating the bank transfer in a IRC chat.
*/
@FxmlView
public class AltCoinAccountView extends ActivatableViewAndModel<GridPane, AltCoinAccountViewModel> implements Wizard.Step {
@FXML HBox buttonsHBox;
@FXML InputTextField ircNickNameTextField;
@FXML Button saveButton;
@FXML ComboBox<FiatAccount.Type> typesComboBox;
@FXML ComboBox<String> currencyComboBox;
private Wizard wizard;
@Inject
public AltCoinAccountView(AltCoinAccountViewModel model) {
super(model);
}
@Override
public void doActivate() {
ircNickNameTextField.setValidator(model.getNickNameValidator());
typesComboBox.setItems(model.getAllTypes());
typesComboBox.setConverter(model.getTypesConverter());
// we use a custom cell for deactivating non IRC items, later we use the standard cell and the StringConverter
typesComboBox.setCellFactory(new Callback<ListView<FiatAccount.Type>, ListCell<FiatAccount.Type>>() {
@Override
public ListCell<FiatAccount.Type> call(ListView<FiatAccount.Type> p) {
return new ListCell<FiatAccount.Type>() {
@Override
protected void updateItem(FiatAccount.Type item, boolean empty) {
super.updateItem(item, empty);
setText(model.getBankAccountType(item));
if (item == null || empty) {
setGraphic(null);
}
else if (item != FiatAccount.Type.IRC) {
setOpacity(0.3);
setDisable(true);
}
}
};
}
});
typesComboBox.getSelectionModel().select(0);
currencyComboBox.setItems(model.getAllCurrencyCodes());
currencyComboBox.setConverter(model.getCurrencyConverter());
// we use a custom cell for deactivating non EUR items, later we use the standard cell and the StringConverter
currencyComboBox.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
@Override
public ListCell<String> call(ListView<String> p) {
return new ListCell<String>() {
@Override
protected void updateItem(String currencyCode, boolean empty) {
super.updateItem(currencyCode, empty);
if (currencyCode == null || empty) {
setGraphic(null);
}
else {
setText(currencyCode + " (" + CurrencyUtil.getDisplayName(currencyCode) + ")");
if (!currencyCode.equals("EUR")) {
setOpacity(0.3);
setDisable(true);
}
}
}
};
}
});
currencyComboBox.getSelectionModel().select(0);
setupListeners();
setupBindings();
Platform.runLater(() -> Popups.openInfoPopup("Demo setup for simulating the banking transfer",
"For demo purposes we use a special setup so that users can simulate the banking transfer when " +
"meeting in an IRC chat room.\n" +
"You need to define your IRC nickname and later in the trade process you can find your " +
"trading partner with his IRC nickname in the chat room and simulate the bank transfer " +
"activities, which are:\n\n" +
"1. Bitcoin buyer indicates that he has started the bank transfer.\n\n" +
"2. Bitcoin seller confirms that he has received the national currency from the " +
"bank transfer."));
}
@Override
public void setWizard(Wizard wizard) {
this.wizard = wizard;
}
@Override
public void hideWizardNavigation() {
}
@FXML
void onSelectType() {
model.setType(typesComboBox.getSelectionModel().getSelectedItem());
}
@FXML
void onSelectCurrency() {
model.setCurrencyCode(currencyComboBox.getSelectionModel().getSelectedItem());
}
@FXML
void onSave() {
boolean isValid = model.requestSaveBankAccount().isValid;
if (wizard != null && isValid)
wizard.nextStep(this);
}
@FXML
void onOpenSetupHelp() {
Help.openWindow(HelpId.SETUP_FIAT_ACCOUNT);
}
@FXML
void onOpenIRC() {
try {
Utilities.openWebPage("https://webchat.freenode.net/?channels=bitsquare-trading");
} catch (Exception e) {
log.error(e.getMessage());
Popups.openWarningPopup("Warning", "Opening browser failed. Please check your internet " +
"connection.");
}
}
private void setupListeners() {
model.type.addListener((ov, oldValue, newValue) -> {
if (newValue != null)
typesComboBox.getSelectionModel().select(typesComboBox.getItems().indexOf(newValue));
else
typesComboBox.getSelectionModel().clearSelection();
});
model.currencyCode.addListener((ov, oldValue, newValue) -> {
if (newValue != null)
currencyComboBox.getSelectionModel().select(currencyComboBox.getItems().indexOf(newValue));
else
currencyComboBox.getSelectionModel().clearSelection();
});
}
private void setupBindings() {
// input
ircNickNameTextField.textProperty().bindBidirectional(model.ircNickName);
saveButton.disableProperty().bind(model.saveButtonDisable);
}
}

View file

@ -0,0 +1,142 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.altcoin;
import io.bitsquare.common.model.ActivatableWithDataModel;
import io.bitsquare.common.model.ViewModel;
import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.gui.util.validation.BankAccountNumberValidator;
import io.bitsquare.gui.util.validation.InputValidator;
import io.bitsquare.locale.BSResources;
import io.bitsquare.locale.CurrencyUtil;
import com.google.inject.Inject;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
class AltCoinAccountViewModel extends ActivatableWithDataModel<AltCoinAccountDataModel> implements ViewModel {
private final InputValidator nickNameValidator;
final StringProperty ircNickName = new SimpleStringProperty();
final StringProperty currencyCode = new SimpleStringProperty();
final BooleanProperty saveButtonDisable = new SimpleBooleanProperty(true);
final ObjectProperty<FiatAccount.Type> type = new SimpleObjectProperty<>();
@Inject
public AltCoinAccountViewModel(AltCoinAccountDataModel dataModel, BankAccountNumberValidator nickNameValidator) {
super(dataModel);
this.nickNameValidator = nickNameValidator;
// input
ircNickName.bindBidirectional(dataModel.nickName);
type.bindBidirectional(dataModel.type);
currencyCode.bindBidirectional(dataModel.currencyCode);
dataModel.nickName.addListener((ov, oldValue, newValue) -> validateInput());
}
InputValidator.ValidationResult requestSaveBankAccount() {
InputValidator.ValidationResult result = validateInput();
if (result.isValid) {
dataModel.saveBankAccount();
}
return result;
}
StringConverter<FiatAccount.Type> getTypesConverter() {
return new StringConverter<FiatAccount.Type>() {
@Override
public String toString(FiatAccount.Type TypeInfo) {
return BSResources.get(TypeInfo.toString());
}
@Override
public FiatAccount.Type fromString(String s) {
return null;
}
};
}
String getBankAccountType(FiatAccount.Type fiatAccountType) {
return fiatAccountType != null ? BSResources.get(fiatAccountType.toString()) : "";
}
StringConverter<String> getCurrencyConverter() {
return new StringConverter<String>() {
@Override
public String toString(String currencyCode) {
return currencyCode + " (" + CurrencyUtil.getDisplayName(currencyCode) + ")";
}
@Override
public String fromString(String s) {
return null;
}
};
}
ObservableList<FiatAccount.Type> getAllTypes() {
return dataModel.allTypes;
}
ObservableList<String> getAllCurrencyCodes() {
return dataModel.allCurrencyCodes;
}
InputValidator getNickNameValidator() {
return nickNameValidator;
}
void setType(FiatAccount.Type type) {
dataModel.setType(type);
validateInput();
}
void setCurrencyCode(String currencyCode) {
dataModel.setCurrencyCode(currencyCode);
validateInput();
}
private InputValidator.ValidationResult validateInput() {
InputValidator.ValidationResult result = nickNameValidator.validate(dataModel.nickName.get());
if (dataModel.currencyCode.get() == null)
result = new InputValidator.ValidationResult(false,
"You have not selected a currency");
if (result.isValid) {
if (dataModel.type.get() == null)
result = new InputValidator.ValidationResult(false,
"You have not selected a payments method");
}
saveButtonDisable.set(!result.isValid);
return result;
}
}

View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import io.bitsquare.gui.components.InfoDisplay?>
<?import io.bitsquare.gui.components.TitledGroupBg?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.content.changepassword.ChangePasswordView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<TitledGroupBg text="Change password" GridPane.rowSpan="5"/>
<Label text="Enter old password:">
<GridPane.margin>
<Insets top="10.0"/>
</GridPane.margin>
</Label>
<PasswordField fx:id="oldPasswordField" promptText="Enter your old password" GridPane.columnIndex="1">
<GridPane.margin>
<Insets top="10.0"/>
</GridPane.margin>
</PasswordField>
<Label text="Enter new password:" GridPane.rowIndex="1"/>
<PasswordField fx:id="passwordField" promptText="Min. 8 characters" GridPane.columnIndex="1"
GridPane.rowIndex="1"/>
<Label text="Repeat password:" GridPane.rowIndex="2"/>
<PasswordField fx:id="repeatedPasswordField" promptText="Repeat password" GridPane.columnIndex="1"
GridPane.rowIndex="2"/>
<HBox fx:id="buttonsHBox" spacing="10" GridPane.columnIndex="1" GridPane.rowIndex="3">
<Button fx:id="saveButton" text="Save password" onAction="#onSaved" disable="true" defaultButton="true"/>
<Button fx:id="skipButton" text="I will not use a password" onAction="#onSkipped"/>
<GridPane.margin>
<Insets bottom="5"/>
</GridPane.margin>
</HBox>
<InfoDisplay gridPane="$root" onAction="#onOpenHelp" rowIndex="4"
text="Protect your wallet with a strong password. You need to enter the password any time you withdraw Bitcoin from your trading wallets. You can change the password later in the settings. Open the help menu for more information."/>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
</rowConstraints>
</GridPane>

View file

@ -0,0 +1,83 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.changepassword;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.InitializableView;
import io.bitsquare.common.view.Wizard;
import io.bitsquare.gui.main.help.Help;
import io.bitsquare.gui.main.help.HelpId;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.*;
@FxmlView
public class ChangePasswordView extends InitializableView<GridPane, ChangePasswordViewModel> implements Wizard.Step {
@FXML HBox buttonsHBox;
@FXML Button saveButton, skipButton;
@FXML PasswordField oldPasswordField, passwordField, repeatedPasswordField;
private Wizard wizard;
@Inject
private ChangePasswordView(ChangePasswordViewModel model) {
super(model);
}
@Override
public void initialize() {
passwordField.textProperty().bindBidirectional(model.passwordField);
repeatedPasswordField.textProperty().bindBidirectional(model.repeatedPasswordField);
saveButton.disableProperty().bind(model.saveButtonDisabled);
}
@Override
public void setWizard(Wizard wizard) {
this.wizard = wizard;
}
@Override
public void hideWizardNavigation() {
buttonsHBox.getChildren().remove(skipButton);
}
@FXML
private void onSaved() {
if (wizard != null && model.requestSavePassword())
wizard.nextStep(this);
else
log.debug(model.getErrorMessage()); // TODO use validating TF
}
@FXML
private void onOpenHelp() {
Help.openWindow(HelpId.SETUP_PASSWORD);
}
@FXML
private void onSkipped() {
if (wizard != null)
wizard.nextStep(this);
}
}

View file

@ -0,0 +1,89 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.changepassword;
import io.bitsquare.common.model.ViewModel;
import io.bitsquare.gui.util.validation.InputValidator;
import io.bitsquare.gui.util.validation.PasswordValidator;
import com.google.inject.Inject;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
class ChangePasswordViewModel implements ViewModel {
private final PasswordValidator passwordValidator;
private String errorMessage;
final StringProperty passwordField = new SimpleStringProperty();
final StringProperty repeatedPasswordField = new SimpleStringProperty();
final BooleanProperty saveButtonDisabled = new SimpleBooleanProperty(true);
@Inject
public ChangePasswordViewModel(PasswordValidator passwordValidator) {
this.passwordValidator = passwordValidator;
passwordField.addListener((ov) -> saveButtonDisabled.set(!validate()));
repeatedPasswordField.addListener((ov) -> saveButtonDisabled.set(!validate()));
}
boolean requestSavePassword() {
if (validate()) {
savePassword(passwordField.get());
return true;
}
return false;
}
void savePassword(String password) {
//TODO Implement password encryption for wallet
}
String getErrorMessage() {
return errorMessage;
}
private boolean validate() {
InputValidator.ValidationResult result = passwordValidator.validate(passwordField.get());
if (result.isValid) {
result = passwordValidator.validate(repeatedPasswordField.get());
if (result.isValid) {
if (passwordField.get().equals(repeatedPasswordField.get()))
return true;
else
errorMessage = "The 2 passwords does not match.";
}
else {
errorMessage = result.errorMessage;
}
}
else {
errorMessage = result.errorMessage;
}
return false;
}
}

View file

@ -0,0 +1,170 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.fiat;
import io.bitsquare.common.model.Activatable;
import io.bitsquare.common.model.DataModel;
import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.locale.Country;
import io.bitsquare.locale.CountryUtil;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.Region;
import io.bitsquare.user.AccountSettings;
import io.bitsquare.user.User;
import com.google.inject.Inject;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
class FiatAccountDataModel implements Activatable, DataModel {
private final User user;
private final AccountSettings accountSettings;
final StringProperty title = new SimpleStringProperty();
final StringProperty holderName = new SimpleStringProperty();
final StringProperty primaryID = new SimpleStringProperty();
final StringProperty secondaryID = new SimpleStringProperty();
final StringProperty primaryIDPrompt = new SimpleStringProperty();
final StringProperty secondaryIDPrompt = new SimpleStringProperty();
final StringProperty currencyCode = new SimpleStringProperty();
final BooleanProperty countryNotInAcceptedCountriesList = new SimpleBooleanProperty();
final ObjectProperty<FiatAccount.Type> type = new SimpleObjectProperty<>();
final ObjectProperty<Country> country = new SimpleObjectProperty<>();
final ObservableList<FiatAccount.Type> allTypes = FXCollections.observableArrayList(FiatAccount.Type
.getAllBankAccountTypes());
final ObservableList<FiatAccount> allFiatAccounts = FXCollections.observableArrayList();
final ObservableList<String> allCurrencyCodes = FXCollections.observableArrayList(CurrencyUtil
.getAllCurrencyCodes());
final ObservableList<Region> allRegions = FXCollections.observableArrayList(CountryUtil.getAllRegions());
@Inject
public FiatAccountDataModel(User user, AccountSettings accountSettings) {
this.user = user;
this.accountSettings = accountSettings;
}
@Override
public void activate() {
allFiatAccounts.setAll(user.fiatAccountsObservableList());
}
@Override
public void deactivate() {
// no-op
}
void saveBankAccount() {
FiatAccount fiatAccount = new FiatAccount(type.get(),
currencyCode.get(),
country.get(),
title.get(),
holderName.get(),
primaryID.get(),
secondaryID.get());
user.addFiatAccount(fiatAccount);
allFiatAccounts.setAll(user.fiatAccountsObservableList());
countryNotInAcceptedCountriesList.set(!accountSettings.getAcceptedCountries().contains(country.get()));
reset();
}
void removeBankAccount() {
user.removeFiatAccount(user.currentFiatAccountProperty().get());
allFiatAccounts.setAll(user.fiatAccountsObservableList());
reset();
}
// We ask the user if he likes to add his own bank account country to the accepted country list if he has not
// already added it before
void addCountryToAcceptedCountriesList() {
accountSettings.addAcceptedCountry(country.get());
countryNotInAcceptedCountriesList.set(false);
}
void selectBankAccount(FiatAccount fiatAccount) {
user.setCurrentFiatAccountProperty(fiatAccount);
if (fiatAccount != null) {
title.set(fiatAccount.nameOfBank);
holderName.set(fiatAccount.accountHolderName);
primaryID.set(fiatAccount.accountPrimaryID);
secondaryID.set(fiatAccount.accountSecondaryID);
primaryIDPrompt.set(fiatAccount.type.primaryId);
secondaryIDPrompt.set(fiatAccount.type.secondaryId);
type.set(fiatAccount.type);
country.set(fiatAccount.country);
currencyCode.set(fiatAccount.currencyCode);
}
else {
reset();
}
}
ObservableList<Country> getAllCountriesFor(Region selectedRegion) {
return FXCollections.observableArrayList(CountryUtil.getAllCountriesFor(selectedRegion));
}
void setType(FiatAccount.Type type) {
this.type.set(type);
if (type != null) {
primaryIDPrompt.set(type.primaryId);
secondaryIDPrompt.set(type.secondaryId);
}
else {
primaryIDPrompt.set(null);
secondaryIDPrompt.set(null);
}
}
void setCountry(Country country) {
this.country.set(country);
}
void setCurrencyCode(String currencyCode) {
this.currencyCode.set(currencyCode);
}
private void reset() {
title.set(null);
holderName.set(null);
primaryID.set(null);
secondaryID.set(null);
primaryIDPrompt.set(null);
secondaryIDPrompt.set(null);
type.set(null);
country.set(null);
currencyCode.set(null);
}
}

View file

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import io.bitsquare.gui.components.InfoDisplay?>
<?import io.bitsquare.gui.components.InputTextField?>
<?import io.bitsquare.gui.components.TitledGroupBg?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.content.fiat.FiatAccountView" hgap="5.0"
vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<!--
Setup
-->
<TitledGroupBg text="Setup your payments account" GridPane.rowSpan="8"/>
<Label text="Payments method:" GridPane.rowIndex="0">
<GridPane.margin>
<Insets top="10"/>
</GridPane.margin>
</Label>
<ComboBox fx:id="typesComboBox" promptText="Select payments method" onAction="#onSelectType"
GridPane.rowIndex="0" GridPane.columnIndex="1">
<GridPane.margin>
<Insets top="10"/>
</GridPane.margin>
</ComboBox>
<Label text="Name of Bank:" GridPane.rowIndex="1"/>
<InputTextField fx:id="nameOfBankTextField" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<Label text="Location of Bank:" GridPane.rowIndex="2"/>
<HBox GridPane.columnIndex="1" GridPane.rowIndex="2" spacing="10">
<ComboBox fx:id="regionComboBox" promptText="Select region"
onAction="#onSelectRegion" prefWidth="150.0"/>
<ComboBox fx:id="countryComboBox" promptText="Select country" onAction="#onSelectCountry"
visible="false" prefWidth="150.0"/>
</HBox>
<Label text="Your name:" GridPane.rowIndex="3"/>
<InputTextField fx:id="holderNameTextField" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
<Label text="IBAN:" GridPane.rowIndex="4"/>
<InputTextField fx:id="primaryIDTextField" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
<Label text="BIC:" GridPane.rowIndex="5"/>
<InputTextField fx:id="secondaryIDTextField" GridPane.columnIndex="1" GridPane.rowIndex="5"/>
<Label text="Currency:" GridPane.rowIndex="6"/>
<ComboBox fx:id="currencyComboBox" promptText="Select currency"
onAction="#onSelectCurrency" GridPane.columnIndex="1"
GridPane.rowIndex="6"/>
<InfoDisplay gridPane="$root" onAction="#onOpenSetupHelp" rowIndex="7"
text="The payments account data will be saved in a encrypted form to the Bitcoin block chain and will be used in the trade process for account verification."/>
<HBox fx:id="buttonsHBox" GridPane.columnIndex="1" GridPane.rowIndex="8" spacing="10">
<Button fx:id="saveButton" text="Save" onAction="#onSave" defaultButton="true" disable="true"/>
<Button fx:id="completedButton" text="Continue to the next step" onAction="#onCompleted" disable="true"/>
<GridPane.margin>
<Insets top="15.0" bottom="5.0"/>
</GridPane.margin>
</HBox>
<!--
Manage
-->
<TitledGroupBg text="Manage payments accounts" GridPane.rowIndex="9" GridPane.rowSpan="4">
<padding>
<Insets top="40.0"/>
</padding>
<GridPane.margin>
<Insets bottom="-10" left="-10" right="-10" top="20"/>
</GridPane.margin>
</TitledGroupBg>
<Label text="Select payments account:" GridPane.rowIndex="9">
<GridPane.margin>
<Insets top="40"/>
</GridPane.margin>
</Label>
<ComboBox fx:id="selectionComboBox" onAction="#onSelectAccount" GridPane.rowIndex="9"
GridPane.columnIndex="1">
<GridPane.margin>
<Insets top="40"/>
</GridPane.margin>
</ComboBox>
<Label text="Remove selected account:" GridPane.rowIndex="10"/>
<Button fx:id="removeBankAccountButton" text="Remove selected payments account" onAction="#onRemoveAccount"
GridPane.columnIndex="1" GridPane.rowIndex="10" disable="true"/>
<InfoDisplay gridPane="$root" onAction="#onOpenManageAccountsHelp" rowIndex="11"
text="When you change your payments accounts later you need to do the renew the registration and pay the small registration fee."/>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="200.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
</rowConstraints>
</GridPane>

View file

@ -0,0 +1,259 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.fiat;
import io.bitsquare.common.view.ActivatableViewAndModel;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.Wizard;
import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.gui.OverlayManager;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.main.help.Help;
import io.bitsquare.gui.main.help.HelpId;
import io.bitsquare.gui.util.validation.InputValidator;
import io.bitsquare.locale.BSResources;
import io.bitsquare.locale.Country;
import io.bitsquare.locale.Region;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import org.controlsfx.control.action.AbstractAction;
import org.controlsfx.control.action.Action;
import org.controlsfx.dialog.Dialog;
import static javafx.beans.binding.Bindings.createBooleanBinding;
@FxmlView
public class FiatAccountView extends ActivatableViewAndModel<GridPane, FiatAccountViewModel> implements Wizard.Step {
@FXML HBox buttonsHBox;
@FXML ComboBox<Region> regionComboBox;
@FXML ComboBox<Country> countryComboBox;
@FXML InputTextField nameOfBankTextField, holderNameTextField, primaryIDTextField, secondaryIDTextField;
@FXML Button saveButton, completedButton, removeBankAccountButton;
@FXML ComboBox<FiatAccount> selectionComboBox;
@FXML ComboBox<FiatAccount.Type> typesComboBox;
@FXML ComboBox<String> currencyComboBox;
private Wizard wizard;
private final OverlayManager overlayManager;
@Inject
public FiatAccountView(FiatAccountViewModel model, OverlayManager overlayManager) {
super(model);
this.overlayManager = overlayManager;
}
@Override
public void initialize() {
typesComboBox.setItems(model.getAllTypes());
typesComboBox.setConverter(model.getTypesConverter());
selectionComboBox.setConverter(model.getSelectionConverter());
currencyComboBox.setItems(model.getAllCurrencyCodes());
currencyComboBox.setConverter(model.getCurrencyConverter());
regionComboBox.setItems(model.getAllRegions());
regionComboBox.setConverter(model.getRegionConverter());
countryComboBox.setConverter(model.getCountryConverter());
nameOfBankTextField.setValidator(model.getBankAccountNumberValidator());
holderNameTextField.setValidator(model.getBankAccountNumberValidator());
primaryIDTextField.setValidator(model.getBankAccountNumberValidator());
secondaryIDTextField.setValidator(model.getBankAccountNumberValidator());
}
@Override
public void doActivate() {
setupListeners();
setupBindings();
selectionComboBox.setItems(model.getAllBankAccounts());
}
@Override
public void setWizard(Wizard wizard) {
this.wizard = wizard;
}
@Override
public void hideWizardNavigation() {
buttonsHBox.getChildren().remove(completedButton);
}
@FXML
void onSelectAccount() {
if (selectionComboBox.getSelectionModel().getSelectedItem() != null)
model.selectBankAccount(selectionComboBox.getSelectionModel().getSelectedItem());
}
@FXML
void onSelectType() {
model.setType(typesComboBox.getSelectionModel().getSelectedItem());
}
@FXML
void onSelectCurrency() {
model.setCurrencyCode(currencyComboBox.getSelectionModel().getSelectedItem());
}
@FXML
void onSelectRegion() {
countryComboBox.setVisible(true);
Region region = regionComboBox.getSelectionModel().getSelectedItem();
if (region != null)
countryComboBox.setItems(model.getAllCountriesFor(region));
}
@FXML
void onSelectCountry() {
Country country = countryComboBox.getSelectionModel().getSelectedItem();
if (country != null)
model.setCountry(country);
}
@FXML
void onSave() {
InputValidator.ValidationResult result = model.requestSaveBankAccount();
if (result.isValid) {
selectionComboBox.getSelectionModel().select(null);
Popups.openInfoPopup("Your payments account has been saved.",
"You can add more accounts or continue to the next step.");
}
}
@FXML
void onCompleted() {
if (wizard != null)
wizard.nextStep(this);
}
@FXML
void onRemoveAccount() {
model.removeBankAccount();
selectionComboBox.getSelectionModel().select(null);
}
@FXML
void onOpenSetupHelp() {
Help.openWindow(HelpId.SETUP_FIAT_ACCOUNT);
}
@FXML
void onOpenManageAccountsHelp() {
Help.openWindow(HelpId.MANAGE_FIAT_ACCOUNT);
}
private void setupListeners() {
model.type.addListener((ov, oldValue, newValue) -> {
if (newValue != null)
typesComboBox.getSelectionModel().select(typesComboBox.getItems().indexOf(newValue));
else
typesComboBox.getSelectionModel().clearSelection();
});
model.currencyCode.addListener((ov, oldValue, newValue) -> {
if (newValue != null)
currencyComboBox.getSelectionModel().select(currencyComboBox.getItems().indexOf(newValue));
else
currencyComboBox.getSelectionModel().clearSelection();
});
model.country.addListener((ov, oldValue, newValue) -> {
if (newValue != null) {
int regionIndex = regionComboBox.getItems().indexOf(newValue.region);
if (regionIndex >= 0 && regionIndex < regionComboBox.getItems().size())
regionComboBox.getSelectionModel().select(regionComboBox.getItems().indexOf(newValue.region));
int countryIndex = countryComboBox.getItems().indexOf(newValue);
if (countryIndex >= 0 && countryIndex < countryComboBox.getItems().size())
countryComboBox.getSelectionModel().select(countryIndex);
}
else {
regionComboBox.getSelectionModel().clearSelection();
countryComboBox.getSelectionModel().clearSelection();
}
});
model.getCountryNotInAcceptedCountriesList().addListener((ov, oldValue, newValue) -> {
if (newValue) {
overlayManager.blurContent();
List<Action> actions = new ArrayList<>();
actions.add(new AbstractAction(BSResources.get("shared.no")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "NO");
Dialog.Actions.NO.handle(actionEvent);
}
});
actions.add(new AbstractAction(BSResources.get("shared.yes")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "YES");
Dialog.Actions.YES.handle(actionEvent);
}
});
Action response = Popups.openConfirmPopup("Warning", null,
"The country of your payments account is not included in your list of accepted countries" +
".\n\nDo you want to add it automatically?",
actions);
if (Popups.isYes(response))
model.addCountryToAcceptedCountriesList();
overlayManager.removeBlurContent();
}
});
model.getAllBankAccounts().addListener((ListChangeListener<FiatAccount>) change ->
completedButton.setDisable(model.getAllBankAccounts().isEmpty()));
completedButton.setDisable(model.getAllBankAccounts().isEmpty());
}
private void setupBindings() {
// input
nameOfBankTextField.textProperty().bindBidirectional(model.title);
holderNameTextField.textProperty().bindBidirectional(model.holderName);
primaryIDTextField.textProperty().bindBidirectional(model.primaryID);
secondaryIDTextField.textProperty().bindBidirectional(model.secondaryID);
primaryIDTextField.promptTextProperty().bind(model.primaryIDPrompt);
secondaryIDTextField.promptTextProperty().bind(model.secondaryIDPrompt);
selectionComboBox.promptTextProperty().bind(model.selectionPrompt);
selectionComboBox.disableProperty().bind(model.selectionDisable);
saveButton.disableProperty().bind(model.saveButtonDisable);
removeBankAccountButton.disableProperty().bind(createBooleanBinding(() ->
(selectionComboBox.getSelectionModel().selectedIndexProperty().get() == -1),
selectionComboBox.getSelectionModel().selectedIndexProperty()));
}
}

View file

@ -0,0 +1,274 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.fiat;
import io.bitsquare.common.model.ActivatableWithDataModel;
import io.bitsquare.common.model.ViewModel;
import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.gui.util.validation.BankAccountNumberValidator;
import io.bitsquare.gui.util.validation.InputValidator;
import io.bitsquare.locale.BSResources;
import io.bitsquare.locale.Country;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.Region;
import com.google.inject.Inject;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
class FiatAccountViewModel extends ActivatableWithDataModel<FiatAccountDataModel> implements ViewModel {
private final BankAccountNumberValidator bankAccountNumberValidator;
final StringProperty title = new SimpleStringProperty();
final StringProperty holderName = new SimpleStringProperty();
final StringProperty primaryID = new SimpleStringProperty();
final StringProperty secondaryID = new SimpleStringProperty();
final StringProperty primaryIDPrompt = new SimpleStringProperty();
final StringProperty secondaryIDPrompt = new SimpleStringProperty();
final StringProperty selectionPrompt = new SimpleStringProperty();
final StringProperty currencyCode = new SimpleStringProperty();
final BooleanProperty selectionDisable = new SimpleBooleanProperty();
final BooleanProperty saveButtonDisable = new SimpleBooleanProperty(true);
final ObjectProperty<FiatAccount.Type> type = new SimpleObjectProperty<>();
final ObjectProperty<Country> country = new SimpleObjectProperty<>();
@Inject
public FiatAccountViewModel(FiatAccountDataModel dataModel, BankAccountNumberValidator bankAccountNumberValidator) {
super(dataModel);
this.bankAccountNumberValidator = bankAccountNumberValidator;
// input
title.bindBidirectional(dataModel.title);
holderName.bindBidirectional(dataModel.holderName);
primaryID.bindBidirectional(dataModel.primaryID);
secondaryID.bindBidirectional(dataModel.secondaryID);
type.bindBidirectional(dataModel.type);
country.bindBidirectional(dataModel.country);
currencyCode.bindBidirectional(dataModel.currencyCode);
primaryIDPrompt.bind(dataModel.primaryIDPrompt);
secondaryIDPrompt.bind(dataModel.secondaryIDPrompt);
selectionPrompt.set("No bank account available");
selectionDisable.set(true);
dataModel.title.addListener((ov, oldValue, newValue) -> validateInput());
holderName.addListener((ov, oldValue, newValue) -> validateInput());
primaryID.addListener((ov, oldValue, newValue) -> validateInput());
secondaryID.addListener((ov, oldValue, newValue) -> validateInput());
}
@Override
public void doActivate() {
dataModel.allFiatAccounts.addListener((ListChangeListener<FiatAccount>) change -> applyAllBankAccounts());
applyAllBankAccounts();
}
InputValidator.ValidationResult requestSaveBankAccount() {
InputValidator.ValidationResult result = validateInput();
if (result.isValid) {
dataModel.saveBankAccount();
}
return result;
}
void removeBankAccount() {
dataModel.removeBankAccount();
}
void addCountryToAcceptedCountriesList() {
dataModel.addCountryToAcceptedCountriesList();
}
void selectBankAccount(FiatAccount fiatAccount) {
dataModel.selectBankAccount(fiatAccount);
}
StringConverter<FiatAccount.Type> getTypesConverter() {
return new StringConverter<FiatAccount.Type>() {
@Override
public String toString(FiatAccount.Type TypeInfo) {
return BSResources.get(TypeInfo.toString());
}
@Override
public FiatAccount.Type fromString(String s) {
return null;
}
};
}
StringConverter<FiatAccount> getSelectionConverter() {
return new StringConverter<FiatAccount>() {
@Override
public String toString(FiatAccount fiatAccount) {
return fiatAccount.nameOfBank;
}
@Override
public FiatAccount fromString(String s) {
return null;
}
};
}
StringConverter<String> getCurrencyConverter() {
return new StringConverter<String>() {
@Override
public String toString(String currencyCode) {
return currencyCode + " (" + CurrencyUtil.getDisplayName(currencyCode) + ")";
}
@Override
public String fromString(String s) {
return null;
}
};
}
StringConverter<Region> getRegionConverter() {
return new StringConverter<io.bitsquare.locale.Region>() {
@Override
public String toString(io.bitsquare.locale.Region region) {
return region.name;
}
@Override
public io.bitsquare.locale.Region fromString(String s) {
return null;
}
};
}
StringConverter<Country> getCountryConverter() {
return new StringConverter<Country>() {
@Override
public String toString(Country country) {
return country.name;
}
@Override
public Country fromString(String s) {
return null;
}
};
}
ObservableList<FiatAccount.Type> getAllTypes() {
return dataModel.allTypes;
}
ObservableList<FiatAccount> getAllBankAccounts() {
return dataModel.allFiatAccounts;
}
ObservableList<String> getAllCurrencyCodes() {
return dataModel.allCurrencyCodes;
}
ObservableList<Region> getAllRegions() {
return dataModel.allRegions;
}
BooleanProperty getCountryNotInAcceptedCountriesList() {
return dataModel.countryNotInAcceptedCountriesList;
}
ObservableList<Country> getAllCountriesFor(Region selectedRegion) {
return dataModel.getAllCountriesFor(selectedRegion);
}
BankAccountNumberValidator getBankAccountNumberValidator() {
return bankAccountNumberValidator;
}
void setType(FiatAccount.Type type) {
dataModel.setType(type);
validateInput();
}
void setCountry(Country country) {
dataModel.setCountry(country);
validateInput();
}
void setCurrencyCode(String currencyCode) {
dataModel.setCurrencyCode(currencyCode);
validateInput();
}
private void applyAllBankAccounts() {
if (dataModel.allFiatAccounts.isEmpty()) {
selectionPrompt.set("No bank account available");
selectionDisable.set(true);
}
else {
selectionPrompt.set("Select bank account");
selectionDisable.set(false);
}
}
private InputValidator.ValidationResult validateInput() {
InputValidator.ValidationResult result = bankAccountNumberValidator.validate(dataModel.title.get());
if (result.isValid) {
result = bankAccountNumberValidator.validate(dataModel.holderName.get());
if (result.isValid) {
result = bankAccountNumberValidator.validate(dataModel.primaryID.get());
if (result.isValid) {
result = bankAccountNumberValidator.validate(dataModel.secondaryID.get());
if (result.isValid) {
if (dataModel.currencyCode.get() == null)
result = new InputValidator.ValidationResult(false,
"You have not selected a currency");
if (result.isValid) {
if (dataModel.country.get() == null)
result = new InputValidator.ValidationResult(false,
"You have not selected a country of the payments account");
if (result.isValid) {
if (dataModel.type.get() == null)
result = new InputValidator.ValidationResult(false,
"You have not selected a payments method");
}
}
}
}
}
}
saveButtonDisable.set(!result.isValid);
return result;
}
}

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import io.bitsquare.gui.components.InfoDisplay?>
<?import io.bitsquare.gui.components.TitledGroupBg?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.content.password.PasswordView" hgap="5.0"
vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<TitledGroupBg text="Setup password" GridPane.rowSpan="4"/>
<Label text="Enter password:">
<GridPane.margin>
<Insets top="10.0"/>
</GridPane.margin>
</Label>
<PasswordField fx:id="passwordField" promptText="Min. 8 characters" GridPane.columnIndex="1">
<GridPane.margin>
<Insets top="10.0"/>
</GridPane.margin>
</PasswordField>
<Label text="Repeat password:" GridPane.rowIndex="1"/>
<PasswordField fx:id="repeatedPasswordField" promptText="Repeat password" GridPane.columnIndex="1"
GridPane.rowIndex="1"/>
<HBox fx:id="buttonsHBox" spacing="10" GridPane.columnIndex="1" GridPane.rowIndex="2">
<Button fx:id="saveButton" text="Save password" onAction="#onSaved" disable="true" defaultButton="true"/>
<Button fx:id="skipButton" text="I will not use a password" onAction="#onSkipped"/>
<GridPane.margin>
<Insets bottom="5"/>
</GridPane.margin>
</HBox>
<InfoDisplay gridPane="$root" onAction="#onOpenHelp" rowIndex="3"
text="Protect your wallet with a strong password. You need to enter the password any time you withdraw Bitcoin from your trading wallets. You can change the password later in the settings. Open the help menu for more information."/>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
</rowConstraints>
</GridPane>

View file

@ -0,0 +1,83 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.password;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.InitializableView;
import io.bitsquare.common.view.Wizard;
import io.bitsquare.gui.main.help.Help;
import io.bitsquare.gui.main.help.HelpId;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.*;
@FxmlView
public class PasswordView extends InitializableView<GridPane, PasswordViewModel> implements Wizard.Step {
@FXML HBox buttonsHBox;
@FXML Button saveButton, skipButton;
@FXML PasswordField oldPasswordField, passwordField, repeatedPasswordField;
private Wizard wizard;
@Inject
private PasswordView(PasswordViewModel model) {
super(model);
}
@Override
public void initialize() {
passwordField.textProperty().bindBidirectional(model.passwordField);
repeatedPasswordField.textProperty().bindBidirectional(model.repeatedPasswordField);
saveButton.disableProperty().bind(model.saveButtonDisabled);
}
@Override
public void setWizard(Wizard wizard) {
this.wizard = wizard;
}
@Override
public void hideWizardNavigation() {
buttonsHBox.getChildren().remove(skipButton);
}
@FXML
private void onSaved() {
if (wizard != null && model.requestSavePassword())
wizard.nextStep(this);
else
log.debug(model.getErrorMessage()); // TODO use validating passwordTF
}
@FXML
private void onSkipped() {
if (wizard != null)
wizard.nextStep(this);
}
@FXML
private void onOpenHelp() {
Help.openWindow(HelpId.SETUP_PASSWORD);
}
}

View file

@ -0,0 +1,87 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.password;
import io.bitsquare.common.model.ViewModel;
import io.bitsquare.gui.util.validation.InputValidator;
import io.bitsquare.gui.util.validation.PasswordValidator;
import com.google.inject.Inject;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
class PasswordViewModel implements ViewModel {
private final PasswordValidator passwordValidator;
private String errorMessage;
final StringProperty passwordField = new SimpleStringProperty();
final StringProperty repeatedPasswordField = new SimpleStringProperty();
final BooleanProperty saveButtonDisabled = new SimpleBooleanProperty(true);
@Inject
public PasswordViewModel(PasswordValidator passwordValidator) {
this.passwordValidator = passwordValidator;
passwordField.addListener((ov) -> saveButtonDisabled.set(!validate()));
repeatedPasswordField.addListener((ov) -> saveButtonDisabled.set(!validate()));
}
boolean requestSavePassword() {
if (validate()) {
savePassword(passwordField.get());
return true;
}
return false;
}
void savePassword(String password) {
//TODO Implement password encryption for wallet
}
String getErrorMessage() {
return errorMessage;
}
private boolean validate() {
InputValidator.ValidationResult result = passwordValidator.validate(passwordField.get());
if (result.isValid) {
result = passwordValidator.validate(repeatedPasswordField.get());
if (result.isValid) {
if (passwordField.get().equals(repeatedPasswordField.get()))
return true;
else
errorMessage = "The 2 passwords does not match.";
}
else {
errorMessage = result.errorMessage;
}
}
else {
errorMessage = result.errorMessage;
}
return false;
}
}

View file

@ -0,0 +1,130 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.registration;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.common.model.DataModel;
import io.bitsquare.user.User;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.inject.Inject;
import javax.annotation.Nullable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class RegistrationDataModel implements DataModel {
private static final Logger log = LoggerFactory.getLogger(RegistrationDataModel.class);
private final WalletService walletService;
private final User user;
private String transactionId;
private AddressEntry addressEntry;
final BooleanProperty isWalletFunded = new SimpleBooleanProperty();
final BooleanProperty payFeeSuccess = new SimpleBooleanProperty();
final StringProperty payFeeErrorMessage = new SimpleStringProperty();
@Inject
public RegistrationDataModel(WalletService walletService, User user) {
this.walletService = walletService;
this.user = user;
if (walletService != null && walletService.getWallet() != null) {
addressEntry = walletService.getRegistrationAddressEntry();
walletService.addBalanceListener(new BalanceListener(getAddressEntry().getAddress()) {
@Override
public void onBalanceChanged(@NotNull Coin balance) {
updateBalance(balance);
}
});
updateBalance(walletService.getBalanceForAddress(getAddressEntry().getAddress()));
}
}
void payFee() {
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {
@Override
public void onSuccess(@Nullable Transaction transaction) {
log.debug("payRegistrationFee onSuccess");
if (transaction != null) {
transactionId = transaction.getHashAsString();
log.info("payRegistrationFee onSuccess tx id:" + transaction.getHashAsString());
if (getAddressEntry() != null)
user.setAccountID(getAddressEntry().toString());
payFeeSuccess.set(true);
}
}
@Override
public void onFailure(@NotNull Throwable t) {
log.debug("payRegistrationFee onFailure");
payFeeErrorMessage.set("Fee payment failed with error: " + t.getMessage());
}
};
try {
walletService.payRegistrationFee(user.getStringifiedBankAccounts(), callback);
} catch (InsufficientMoneyException e) {
payFeeErrorMessage.set("Fee payment failed with error: " + e.getMessage());
}
}
WalletService getWalletService() {
return walletService;
}
Coin getFeeAsCoin() {
return FeePolicy.REGISTRATION_FEE;
}
String getTransactionId() {
return transactionId;
}
AddressEntry getAddressEntry() {
return addressEntry;
}
private void updateBalance(@NotNull Coin balance) {
isWalletFunded.set(balance.compareTo(getFeeAsCoin()) >= 0);
}
}

View file

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import io.bitsquare.gui.components.AddressTextField?>
<?import io.bitsquare.gui.components.BalanceTextField?>
<?import io.bitsquare.gui.components.InfoDisplay?>
<?import io.bitsquare.gui.components.TitledGroupBg?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.content.registration.RegistrationView" hgap="5.0"
vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<TitledGroupBg text="Complete the registration" GridPane.rowSpan="4"/>
<Label text="Registration costs:" GridPane.rowIndex="0">
<GridPane.margin>
<Insets top="10.0"/>
</GridPane.margin>
</Label>
<TextField fx:id="feeTextField"
GridPane.columnIndex="1"
GridPane.rowIndex="0" editable="false" focusTraversable="false">
<GridPane.margin>
<Insets top="10.0"/>
</GridPane.margin>
</TextField>
<Label text="Registration wallet address:" GridPane.rowIndex="1"/>
<AddressTextField fx:id="addressTextField" GridPane.columnIndex="1" GridPane.rowIndex="1"
focusTraversable="true"/>
<Label text="Registration wallet balance:" GridPane.rowIndex="2">
<GridPane.margin>
<Insets bottom="5.0"/>
</GridPane.margin>
</Label>
<BalanceTextField fx:id="balanceTextField" GridPane.columnIndex="1" GridPane.rowIndex="2"
focusTraversable="false">
<GridPane.margin>
<Insets bottom="5.0"/>
</GridPane.margin>
</BalanceTextField>
<InfoDisplay gridPane="$root" onAction="#onOpenHelp" rowIndex="3"
text="You need to pay a 0.0002 BTC which is needed for storing your encrypted account data in the blockchain. That will be used in the trade process for account verification."/>
<HBox spacing="10" GridPane.columnIndex="1" GridPane.rowIndex="4">
<Button fx:id="payButton" text="Pay registration fee" onAction="#onPayFee" defaultButton="true"/>
<ProgressIndicator fx:id="paymentSpinner" progress="0" visible="false" prefHeight="24"
prefWidth="24"/>
<Label fx:id="paymentSpinnerInfoLabel" text="Payment is in progress..." visible="false">
<padding>
<Insets top="5.0"/>
</padding>
</Label>
<GridPane.margin>
<Insets top="15.0"/>
</GridPane.margin>
</HBox>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="200.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
</rowConstraints>
</GridPane>

View file

@ -0,0 +1,144 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.registration;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.InitializableView;
import io.bitsquare.common.view.Wizard;
import io.bitsquare.gui.OverlayManager;
import io.bitsquare.gui.components.AddressTextField;
import io.bitsquare.gui.components.BalanceTextField;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.main.help.Help;
import io.bitsquare.gui.main.help.HelpId;
import io.bitsquare.locale.BSResources;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import org.controlsfx.control.action.AbstractAction;
import org.controlsfx.control.action.Action;
import org.controlsfx.dialog.Dialog;
@FxmlView
public class RegistrationView extends InitializableView<GridPane, RegistrationViewModel> implements Wizard.Step {
@FXML TextField feeTextField;
@FXML AddressTextField addressTextField;
@FXML BalanceTextField balanceTextField;
@FXML Button payButton;
@FXML Label paymentSpinnerInfoLabel;
@FXML ProgressIndicator paymentSpinner;
private Wizard wizard;
private final OverlayManager overlayManager;
@Inject
private RegistrationView(RegistrationViewModel model, OverlayManager overlayManager) {
super(model);
this.overlayManager = overlayManager;
}
@Override
public void initialize() {
feeTextField.setText(model.getFeeAsString());
addressTextField.setAmountAsCoin(model.getFeeAsCoin());
addressTextField.setPaymentLabel(model.getPaymentLabel());
addressTextField.setAddress(model.getAddressAsString());
// TODO find better solution
addressTextField.setOverlayManager(overlayManager);
balanceTextField.setup(model.getWalletService(), model.address.get(),
model.getFormatter());
payButton.disableProperty().bind(model.isPayButtonDisabled);
model.requestPlaceOfferErrorMessage.addListener((o, oldValue, newValue) -> {
if (newValue != null) {
Popups.openErrorPopup(BSResources.get("shared.error"),
BSResources.get("An error occurred when paying the registration fee"),
newValue);
}
});
paymentSpinnerInfoLabel.visibleProperty().bind(model.isPaymentSpinnerVisible);
model.isPaymentSpinnerVisible.addListener((ov, oldValue, newValue) -> {
paymentSpinner.setProgress(newValue ? -1 : 0);
paymentSpinner.setVisible(newValue);
});
model.showTransactionPublishedScreen.addListener((o, oldValue, newValue) -> {
if (newValue) {
overlayManager.blurContent();
List<Action> actions = new ArrayList<>();
/* actions.add(new AbstractAction(BSResources.get("shared.copyTxId")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "COPY");
Utilities.copyToClipboard(presentationModel.getTransactionId());
}
});*/
actions.add(new AbstractAction(BSResources.get("shared.close")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "CLOSE");
if (wizard != null)
wizard.nextStep(RegistrationView.this);
Dialog.Actions.CLOSE.handle(actionEvent);
}
});
Popups.openInfoPopup(BSResources.get("You have been successfully registered."),
BSResources.get("Congratulation you have been successfully registered.\n\n" +
" You can now start trading."),
actions);
}
});
}
@Override
public void setWizard(Wizard wizard) {
this.wizard = wizard;
}
@Override
public void hideWizardNavigation() {
}
@FXML
private void onPayFee() {
model.payFee();
}
@FXML
private void onOpenHelp() {
Help.openWindow(HelpId.PAY_ACCOUNT_FEE);
}
}

View file

@ -0,0 +1,125 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.registration;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.model.ViewModel;
import io.bitsquare.common.model.WithDataModel;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.BSResources;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import com.google.inject.Inject;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
class RegistrationViewModel extends WithDataModel<RegistrationDataModel> implements ViewModel {
final BooleanProperty isPayButtonDisabled = new SimpleBooleanProperty(true);
final StringProperty requestPlaceOfferErrorMessage = new SimpleStringProperty();
final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
final BooleanProperty isPaymentSpinnerVisible = new SimpleBooleanProperty(false);
// That is needed for the addressTextField
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
private final BSFormatter formatter;
@Inject
public RegistrationViewModel(RegistrationDataModel dataModel, BSFormatter formatter) {
super(dataModel);
this.formatter = formatter;
if (dataModel.getAddressEntry() != null) {
address.set(dataModel.getAddressEntry().getAddress());
}
dataModel.isWalletFunded.addListener((ov, oldValue, newValue) -> {
if (newValue)
validateInput();
});
validateInput();
dataModel.payFeeSuccess.addListener((ov, oldValue, newValue) -> {
isPayButtonDisabled.set(newValue);
showTransactionPublishedScreen.set(newValue);
isPaymentSpinnerVisible.set(false);
});
dataModel.payFeeErrorMessage.addListener((ov, oldValue, newValue) -> {
if (newValue != null) {
requestPlaceOfferErrorMessage.set(newValue);
isPaymentSpinnerVisible.set(false);
}
});
}
void payFee() {
dataModel.payFeeErrorMessage.set(null);
dataModel.payFeeSuccess.set(false);
isPayButtonDisabled.set(true);
isPaymentSpinnerVisible.set(true);
dataModel.payFee();
}
WalletService getWalletService() {
return dataModel.getWalletService();
}
BSFormatter getFormatter() {
return formatter;
}
Coin getFeeAsCoin() {
return dataModel.getFeeAsCoin();
}
String getAddressAsString() {
return dataModel.getAddressEntry() != null ? dataModel.getAddressEntry().getAddress().toString() : "";
}
String getPaymentLabel() {
return BSResources.get("Bitsquare account registration fee");
}
String getFeeAsString() {
return formatter.formatCoinWithCode(dataModel.getFeeAsCoin());
}
String getTransactionId() {
return dataModel.getTransactionId();
}
private void validateInput() {
isPayButtonDisabled.set(!(dataModel.isWalletFunded.get()));
}
}

View file

@ -0,0 +1,106 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.restrictions;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.common.model.Activatable;
import io.bitsquare.common.model.DataModel;
import io.bitsquare.locale.Country;
import io.bitsquare.locale.CountryUtil;
import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.locale.Region;
import io.bitsquare.user.AccountSettings;
import com.google.inject.Inject;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
class RestrictionsDataModel implements Activatable, DataModel {
private final AccountSettings accountSettings;
final ObservableList<String> languageCodes = FXCollections.observableArrayList();
final ObservableList<Country> countries = FXCollections.observableArrayList();
final ObservableList<Arbitrator> arbitrators = FXCollections.observableArrayList();
final ObservableList<String> allLanguageCodes = FXCollections.observableArrayList(LanguageUtil
.getAllLanguageLocaleCodes());
final ObservableList<Region> allRegions = FXCollections.observableArrayList(CountryUtil.getAllRegions());
@Inject
public RestrictionsDataModel(AccountSettings accountSettings) {
this.accountSettings = accountSettings;
}
@Override
public void activate() {
countries.setAll(accountSettings.getAcceptedCountries());
languageCodes.setAll(accountSettings.getAcceptedLanguageLocaleCodes());
arbitrators.setAll(accountSettings.getAcceptedArbitrators());
}
@Override
public void deactivate() {
}
ObservableList<Country> getAllCountriesFor(Region selectedRegion) {
return FXCollections.observableArrayList(CountryUtil.getAllCountriesFor(selectedRegion));
}
void updateArbitratorList() {
arbitrators.setAll(accountSettings.getAcceptedArbitrators());
}
void addLanguageCode(String code) {
if (code != null && !languageCodes.contains(code)) {
languageCodes.add(code);
accountSettings.addAcceptedLanguageLocale(code);
}
}
void removeLanguage(String code) {
languageCodes.remove(code);
accountSettings.removeAcceptedLanguageLocale(code);
}
void addCountry(Country country) {
if (!countries.contains(country) && country != null) {
countries.add(country);
accountSettings.addAcceptedCountry(country);
}
}
ObservableList<Country> getListWithAllEuroCountries() {
// TODO use Set instead of List
// In addAcceptedCountry there is a check to no add duplicates, so it works correctly for now
CountryUtil.getAllEuroCountries().stream().forEach(accountSettings::addAcceptedCountry);
countries.setAll(accountSettings.getAcceptedCountries());
return countries;
}
void removeCountry(Country country) {
countries.remove(country);
accountSettings.removeAcceptedCountry(country);
}
void removeArbitrator(Arbitrator arbitrator) {
arbitrators.remove(arbitrator);
accountSettings.removeAcceptedArbitrator(arbitrator);
}
}

View file

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import io.bitsquare.gui.components.InfoDisplay?>
<?import io.bitsquare.gui.components.TitledGroupBg?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.content.restrictions.RestrictionsView" hgap="5.0"
vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<!--
languages
-->
<TitledGroupBg text="Add languages" GridPane.rowSpan="3"/>
<Label text="Accepted languages:" GridPane.rowIndex="0" GridPane.valignment="TOP">
<padding>
<Insets top="10"/>
</padding>
</Label>
<ListView fx:id="languagesListView" GridPane.columnIndex="1" GridPane.rowIndex="0" prefHeight="80.0"/>
<ComboBox fx:id="languageComboBox" onAction="#onAddLanguage" promptText="Add language"
GridPane.columnIndex="1" GridPane.rowIndex="1"
prefWidth="150.0"/>
<InfoDisplay gridPane="$root" onAction="#onOpenLanguagesHelp" rowIndex="2"
text="Trade with users who have at least 1 shared language."/>
<!--
countries
-->
<TitledGroupBg text="Add countries" GridPane.rowIndex="3" GridPane.rowSpan="3">
<padding>
<Insets top="50.0"/>
</padding>
<GridPane.margin>
<Insets bottom="-10" left="-10" right="-10" top="30"/>
</GridPane.margin>
</TitledGroupBg>
<Label text="Accepted countries:" GridPane.rowIndex="3" GridPane.valignment="TOP">
<GridPane.margin>
<Insets top="50"/>
</GridPane.margin>
</Label>
<ListView fx:id="countriesListView" GridPane.columnIndex="1" GridPane.rowIndex="3" prefHeight="80.0"
>
<GridPane.margin>
<Insets top="40"/>
</GridPane.margin>
</ListView>
<HBox GridPane.columnIndex="1" GridPane.rowIndex="4" spacing="10">
<ComboBox fx:id="regionComboBox" onAction="#onSelectRegion" promptText="Select region"
prefWidth="150.0"/>
<ComboBox fx:id="countryComboBox" onAction="#onAddCountry" promptText="Add country" visible="false"
prefWidth="150.0"/>
<Button fx:id="addAllEuroCountriesButton" text="Add Euro countries" onAction="#onAddAllEuroCountries"
prefWidth="150.0" visible="false"/>
</HBox>
<InfoDisplay gridPane="$root" onAction="#onOpenCountriesHelp" rowIndex="5"
text="Restrict trades with these payments account countries."/>
<!--
arbitrators
-->
<TitledGroupBg text="Add arbitrators" GridPane.rowIndex="6" GridPane.rowSpan="3">
<padding>
<Insets top="50.0"/>
</padding>
<GridPane.margin>
<Insets bottom="-10" left="-10" right="-10" top="30"/>
</GridPane.margin>
</TitledGroupBg>
<Label text="Accepted arbitrators:" GridPane.rowIndex="6" GridPane.valignment="TOP">
<GridPane.margin>
<Insets top="50"/>
</GridPane.margin>
</Label>
<ListView fx:id="arbitratorsListView" GridPane.columnIndex="1" GridPane.rowIndex="6" prefHeight="80.0">
<GridPane.margin>
<Insets top="40"/>
</GridPane.margin>
</ListView>
<Button text="Add arbitrator" onAction="#onOpenArbitratorScreen" GridPane.columnIndex="1"
GridPane.rowIndex="7"/>
<InfoDisplay gridPane="$root" onAction="#onOpenArbitratorsHelp" rowIndex="8"
text="You need to choose at least 3 arbitrators."/>
<Button fx:id="completedButton" text="Completed" onAction="#onCompleted" disable="true" GridPane.columnIndex="1"
GridPane.rowIndex="9"
defaultButton="true">
<GridPane.margin>
<Insets top="20.0"/>
</GridPane.margin>
</Button>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
</rowConstraints>
</GridPane>

View file

@ -0,0 +1,324 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.restrictions;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.common.view.ActivatableViewAndModel;
import io.bitsquare.common.view.CachingViewLoader;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.View;
import io.bitsquare.common.view.ViewLoader;
import io.bitsquare.common.view.Wizard;
import io.bitsquare.gui.main.account.arbitrator.browser.BrowserView;
import io.bitsquare.gui.main.help.Help;
import io.bitsquare.gui.main.help.HelpId;
import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.locale.Country;
import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.locale.Region;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;
@FxmlView
public class RestrictionsView extends ActivatableViewAndModel<GridPane, RestrictionsViewModel> implements Wizard.Step {
@FXML ListView<String> languagesListView;
@FXML ListView<Country> countriesListView;
@FXML ListView<Arbitrator> arbitratorsListView;
@FXML ComboBox<String> languageComboBox;
@FXML ComboBox<Region> regionComboBox;
@FXML ComboBox<Country> countryComboBox;
@FXML Button completedButton, addAllEuroCountriesButton;
private Wizard wizard;
private final ViewLoader viewLoader;
private final Stage primaryStage;
@Inject
private RestrictionsView(RestrictionsViewModel model, CachingViewLoader viewLoader, Stage primaryStage) {
super(model);
this.viewLoader = viewLoader;
this.primaryStage = primaryStage;
}
@Override
public void initialize() {
initLanguage();
initCountry();
initArbitrators();
completedButton.disableProperty().bind(model.doneButtonDisable);
}
@Override
public void doActivate() {
languagesListView.setItems(model.getLanguageCodes());
countriesListView.setItems(model.getCountryList());
arbitratorsListView.setItems(model.getArbitratorList());
}
@Override
public void setWizard(Wizard wizard) {
this.wizard = wizard;
}
@Override
public void hideWizardNavigation() {
root.getChildren().remove(completedButton);
}
@FXML
private void onAddLanguage() {
model.addLanguage(languageComboBox.getSelectionModel().getSelectedItem());
languageComboBox.getSelectionModel().clearSelection();
}
@FXML
private void onSelectRegion() {
countryComboBox.setVisible(true);
Region region = regionComboBox.getSelectionModel().getSelectedItem();
countryComboBox.setItems(model.getAllCountriesFor(region));
addAllEuroCountriesButton.setVisible(region.code.equals("EU"));
}
@FXML
private void onAddCountry() {
model.addCountry(countryComboBox.getSelectionModel().getSelectedItem());
countryComboBox.getSelectionModel().clearSelection();
}
@FXML
private void onAddAllEuroCountries() {
countriesListView.setItems(model.getListWithAllEuroCountries());
}
@FXML
private void onOpenArbitratorScreen() {
View view = viewLoader.load(BrowserView.class);
showStage(view);
}
@FXML
private void onCompleted() {
if (wizard != null)
wizard.nextStep(this);
}
@FXML
private void onOpenLanguagesHelp() {
Help.openWindow(HelpId.SETUP_RESTRICTION_LANGUAGES);
}
@FXML
private void onOpenCountriesHelp() {
Help.openWindow(HelpId.SETUP_RESTRICTION_COUNTRIES);
}
@FXML
private void onOpenArbitratorsHelp() {
Help.openWindow(HelpId.SETUP_RESTRICTION_ARBITRATORS);
}
private void showStage(View view) {
final Stage stage = new Stage();
stage.setTitle("Arbitrator selection");
stage.setMinWidth(800);
stage.setMinHeight(500);
stage.setWidth(800);
stage.setHeight(600);
stage.setX(primaryStage.getX() + 50);
stage.setY(primaryStage.getY() + 50);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(primaryStage);
Scene scene = new Scene((Parent) view.getRoot(), 800, 600);
stage.setScene(scene);
stage.setOnHidden(windowEvent -> {
if (view instanceof BrowserView)
updateArbitratorList();
});
stage.show();
}
void updateArbitratorList() {
model.updateArbitratorList();
arbitratorsListView.setItems(model.getArbitratorList());
}
private void initLanguage() {
languagesListView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
@Override
public ListCell<String> call(ListView<String> list) {
return new ListCell<String>() {
final Label label = new Label();
final ImageView icon = ImageUtil.getImageViewById(ImageUtil.REMOVE_ICON);
final Button removeButton = new Button("", icon);
final AnchorPane pane = new AnchorPane(label, removeButton);
{
label.setLayoutY(5);
removeButton.setId("icon-button");
AnchorPane.setRightAnchor(removeButton, 0d);
}
@Override
public void updateItem(final String item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
label.setText(LanguageUtil.getDisplayName(item));
removeButton.setOnAction(actionEvent -> removeLanguage(item));
setGraphic(pane);
}
else {
setGraphic(null);
}
}
};
}
});
languageComboBox.setItems(model.getAllLanguageCodes());
languageComboBox.setConverter(new StringConverter<String>() {
@Override
public String toString(String code) {
return LanguageUtil.getDisplayName(code);
}
@Override
public String fromString(String s) {
return null;
}
});
}
private void initCountry() {
regionComboBox.setItems(model.getAllRegions());
regionComboBox.setConverter(new StringConverter<io.bitsquare.locale.Region>() {
@Override
public String toString(io.bitsquare.locale.Region region) {
return region.name;
}
@Override
public io.bitsquare.locale.Region fromString(String s) {
return null;
}
});
countriesListView.setCellFactory(new Callback<ListView<Country>, ListCell<Country>>() {
@Override
public ListCell<Country> call(ListView<Country> list) {
return new ListCell<Country>() {
final Label label = new Label();
final ImageView icon = ImageUtil.getImageViewById(ImageUtil.REMOVE_ICON);
final Button removeButton = new Button("", icon);
final AnchorPane pane = new AnchorPane(label, removeButton);
{
label.setLayoutY(5);
removeButton.setId("icon-button");
AnchorPane.setRightAnchor(removeButton, 0d);
}
@Override
public void updateItem(final Country country, boolean empty) {
super.updateItem(country, empty);
if (country != null && !empty) {
label.setText(country.name);
removeButton.setOnAction(actionEvent -> removeCountry(country));
setGraphic(pane);
}
else {
setGraphic(null);
}
}
};
}
});
countryComboBox.setConverter(new StringConverter<Country>() {
@Override
public String toString(Country country) {
return country.name;
}
@Override
public Country fromString(String s) {
return null;
}
});
}
private void initArbitrators() {
arbitratorsListView.setCellFactory(new Callback<ListView<Arbitrator>, ListCell<Arbitrator>>() {
@Override
public ListCell<Arbitrator> call(ListView<Arbitrator> list) {
return new ListCell<Arbitrator>() {
final Label label = new Label();
final ImageView icon = ImageUtil.getImageViewById(ImageUtil.REMOVE_ICON);
final Button removeButton = new Button("", icon);
final AnchorPane pane = new AnchorPane(label, removeButton);
{
label.setLayoutY(5);
removeButton.setId("icon-button");
AnchorPane.setRightAnchor(removeButton, 0d);
}
@Override
public void updateItem(final Arbitrator item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
label.setText(item.getName());
removeButton.setOnAction(actionEvent -> removeArbitrator(item));
setGraphic(pane);
}
else {
setGraphic(null);
}
}
};
}
});
}
private void removeLanguage(String locale) {
model.removeLanguage(locale);
}
private void removeCountry(Country country) {
model.removeCountry(country);
}
private void removeArbitrator(Arbitrator arbitrator) {
model.removeArbitrator(arbitrator);
}
}

View file

@ -0,0 +1,116 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.restrictions;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.common.model.ActivatableWithDataModel;
import io.bitsquare.common.model.ViewModel;
import io.bitsquare.locale.Country;
import io.bitsquare.locale.Region;
import com.google.inject.Inject;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.ObservableList;
class RestrictionsViewModel extends ActivatableWithDataModel<RestrictionsDataModel> implements ViewModel {
final BooleanProperty doneButtonDisable = new SimpleBooleanProperty(true);
@Inject
public RestrictionsViewModel(RestrictionsDataModel dataModel) {
super(dataModel);
}
@Override
public void doActivate() {
updateDoneButtonDisableState();
}
void addLanguage(String locale) {
dataModel.addLanguageCode(locale);
updateDoneButtonDisableState();
}
void removeLanguage(String locale) {
dataModel.removeLanguage(locale);
updateDoneButtonDisableState();
}
void addCountry(Country country) {
dataModel.addCountry(country);
updateDoneButtonDisableState();
}
void removeCountry(Country country) {
dataModel.removeCountry(country);
updateDoneButtonDisableState();
}
void removeArbitrator(Arbitrator arbitrator) {
dataModel.removeArbitrator(arbitrator);
updateDoneButtonDisableState();
}
void updateArbitratorList() {
dataModel.updateArbitratorList();
updateDoneButtonDisableState();
}
ObservableList<Country> getListWithAllEuroCountries() {
return dataModel.getListWithAllEuroCountries();
}
ObservableList<Country> getAllCountriesFor(Region selectedRegion) {
return dataModel.getAllCountriesFor(selectedRegion);
}
ObservableList<String> getLanguageCodes() {
return dataModel.languageCodes;
}
ObservableList<Region> getAllRegions() {
return dataModel.allRegions;
}
ObservableList<String> getAllLanguageCodes() {
return dataModel.allLanguageCodes;
}
ObservableList<Country> getCountryList() {
return dataModel.countries;
}
ObservableList<Arbitrator> getArbitratorList() {
return dataModel.arbitrators;
}
//TODO Revert size() > -1 to 0(2 later). For mock testing disabled arbitratorList test
private void updateDoneButtonDisableState() {
boolean isValid = dataModel.languageCodes != null && dataModel.languageCodes.size() > 0 &&
dataModel.countries != null && dataModel.countries.size() > 0 &&
dataModel.arbitrators != null && dataModel.arbitrators.size() > -1;
doneButtonDisable.set(!isValid);
}
}

View file

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import io.bitsquare.gui.components.InfoDisplay?>
<?import io.bitsquare.gui.components.TitledGroupBg?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.content.seedwords.SeedWordsView" hgap="5.0"
vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<TitledGroupBg text="Backup your wallet seed words" GridPane.rowSpan="4"/>
<Label text="Wallet seed words:" GridPane.columnIndex="0" GridPane.rowIndex="1" GridPane.valignment="TOP">
<GridPane.margin>
<Insets top="7.0"/>
</GridPane.margin>
</Label>
<TextArea fx:id="seedWordsTextArea" GridPane.columnIndex="1" GridPane.rowIndex="1" wrapText="true"
prefHeight="80">
<font>
<Font size="16.0"/>
</font>
</TextArea>
<Button fx:id="completedButton" text="I have made my backup" onAction="#onCompleted"
GridPane.columnIndex="1" GridPane.rowIndex="2"
defaultButton="true">
<GridPane.margin>
<Insets bottom="5"/>
</GridPane.margin>
</Button>
<InfoDisplay gridPane="$root" onAction="#onOpenHelp" rowIndex="3"
text="You can recreate your wallet our of these words when you lose your wallet. Backup it on paper to have better protection against online theft. Open the help menu for more information."/>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
</rowConstraints>
</GridPane>

View file

@ -0,0 +1,71 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.seedwords;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.InitializableView;
import io.bitsquare.common.view.Wizard;
import io.bitsquare.gui.main.help.Help;
import io.bitsquare.gui.main.help.HelpId;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.*;
@FxmlView
public class SeedWordsView extends InitializableView<GridPane, SeedWordsViewModel> implements Wizard.Step {
@FXML Button completedButton;
@FXML TextArea seedWordsTextArea;
private Wizard wizard;
@Inject
private SeedWordsView(SeedWordsViewModel model) {
super(model);
}
@Override
public void initialize() {
seedWordsTextArea.setText(model.seedWords.get());
}
@Override
public void setWizard(Wizard wizard) {
this.wizard = wizard;
}
@Override
public void hideWizardNavigation() {
root.getChildren().remove(completedButton);
}
@FXML
private void onCompleted() {
if (wizard != null)
wizard.nextStep(this);
}
@FXML
private void onOpenHelp() {
Help.openWindow(HelpId.SETUP_SEED_WORDS);
}
}

View file

@ -0,0 +1,44 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.seedwords;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.model.ViewModel;
import io.bitsquare.gui.util.BSFormatter;
import com.google.inject.Inject;
import java.util.List;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
class SeedWordsViewModel implements ViewModel {
final StringProperty seedWords = new SimpleStringProperty();
@Inject
public SeedWordsViewModel(WalletService walletService, BSFormatter formatter) {
if (walletService.getWallet() != null) {
List<String> mnemonicCode = walletService.getWallet().getKeyChainSeed().getMnemonicCode();
if (mnemonicCode != null) {
seedWords.set(formatter.mnemonicCodeToString(mnemonicCode));
}
}
}
}

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.settings.AccountSettingsView"
prefHeight="660.0" prefWidth="1000.0"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" spacing="5" prefWidth="200" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="20"/>
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="10" AnchorPane.rightAnchor="25" AnchorPane.leftAnchor="250"
AnchorPane.topAnchor="30" prefWidth="720"/>
</AnchorPane>

View file

@ -0,0 +1,165 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.settings;
import io.bitsquare.common.view.ActivatableViewAndModel;
import io.bitsquare.common.view.CachingViewLoader;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.View;
import io.bitsquare.common.view.ViewLoader;
import io.bitsquare.common.view.ViewPath;
import io.bitsquare.common.view.Wizard;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.account.AccountView;
import io.bitsquare.gui.main.account.content.changepassword.ChangePasswordView;
import io.bitsquare.gui.main.account.content.fiat.FiatAccountView;
import io.bitsquare.gui.main.account.content.registration.RegistrationView;
import io.bitsquare.gui.main.account.content.restrictions.RestrictionsView;
import io.bitsquare.gui.main.account.content.seedwords.SeedWordsView;
import io.bitsquare.gui.util.Colors;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
@FxmlView
public class AccountSettingsView extends ActivatableViewAndModel {
private final ViewLoader viewLoader;
private final Navigation navigation;
private MenuItem seedWords, password, restrictions, fiatAccount, registration;
private Navigation.Listener listener;
@FXML private VBox leftVBox;
@FXML private AnchorPane content;
@Inject
private AccountSettingsView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
listener = viewPath -> {
if (viewPath.size() != 4 || viewPath.indexOf(AccountSettingsView.class) != 2)
return;
loadView(viewPath.tip());
};
ToggleGroup toggleGroup = new ToggleGroup();
seedWords = new MenuItem(navigation, toggleGroup, "Wallet seed", SeedWordsView.class);
password = new MenuItem(navigation, toggleGroup, "Wallet password", ChangePasswordView.class);
restrictions = new MenuItem(navigation, toggleGroup, "Arbitrator selection", RestrictionsView.class);
fiatAccount = new MenuItem(navigation, toggleGroup, "Payments account(s)", FiatAccountView.class);
registration = new MenuItem(navigation, toggleGroup, "Renew your account", RegistrationView.class);
leftVBox.getChildren().addAll(seedWords, password, restrictions, fiatAccount, registration);
}
@Override
public void doActivate() {
navigation.addListener(listener);
ViewPath viewPath = navigation.getCurrentPath();
if (viewPath.size() == 3 && viewPath.indexOf(AccountSettingsView.class) == 2) {
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class, SeedWordsView.class);
}
else if (viewPath.size() == 4 && viewPath.indexOf(AccountSettingsView.class) == 2) {
loadView(viewPath.get(3));
}
}
@Override
public void doDeactivate() {
navigation.removeListener(listener);
}
private void loadView(Class<? extends View> viewClass) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
if (view instanceof Wizard.Step)
((Wizard.Step) view).hideWizardNavigation();
if (view instanceof SeedWordsView) seedWords.setSelected(true);
else if (view instanceof ChangePasswordView) password.setSelected(true);
else if (view instanceof RestrictionsView) restrictions.setSelected(true);
else if (view instanceof FiatAccountView) fiatAccount.setSelected(true);
else if (view instanceof RegistrationView) registration.setSelected(true);
}
}
class MenuItem extends ToggleButton {
MenuItem(Navigation navigation, ToggleGroup toggleGroup, String title, Class<? extends View> viewClass) {
setToggleGroup(toggleGroup);
setText(title);
setId("account-settings-item-background-active");
setPrefHeight(40);
setPrefWidth(200);
setAlignment(Pos.CENTER_LEFT);
Label icon = new Label();
icon.setTextFill(Paint.valueOf("#999"));
if (viewClass == SeedWordsView.class)
AwesomeDude.setIcon(icon, AwesomeIcon.INFO_SIGN);
else if (viewClass == RegistrationView.class)
AwesomeDude.setIcon(icon, AwesomeIcon.BRIEFCASE);
else
AwesomeDude.setIcon(icon, AwesomeIcon.EDIT_SIGN);
setGraphic(icon);
setOnAction((event) ->
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class, viewClass));
selectedProperty().addListener((ov, oldValue, newValue) -> {
if (newValue) {
setId("account-settings-item-background-selected");
icon.setTextFill(Colors.BLUE);
}
else {
setId("account-settings-item-background-active");
icon.setTextFill(Paint.valueOf("#999"));
}
});
disableProperty().addListener((ov, oldValue, newValue) -> {
if (newValue) {
setId("account-settings-item-background-disabled");
icon.setTextFill(Paint.valueOf("#ccc"));
}
else {
setId("account-settings-item-background-active");
icon.setTextFill(Paint.valueOf("#999"));
}
});
}
}

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.setup.AccountSetupWizard"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" spacing="5" prefWidth="300" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="20"/>
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="10" AnchorPane.rightAnchor="25" AnchorPane.leftAnchor="350"
AnchorPane.topAnchor="30" prefWidth="620"/>
</AnchorPane>

View file

@ -0,0 +1,218 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.setup;
import io.bitsquare.common.view.ActivatableView;
import io.bitsquare.common.view.CachingViewLoader;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.View;
import io.bitsquare.common.view.ViewLoader;
import io.bitsquare.common.view.Wizard;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.account.content.fiat.FiatAccountView;
import io.bitsquare.gui.main.account.content.password.PasswordView;
import io.bitsquare.gui.main.account.content.registration.RegistrationView;
import io.bitsquare.gui.main.account.content.restrictions.RestrictionsView;
import io.bitsquare.gui.main.account.content.seedwords.SeedWordsView;
import io.bitsquare.gui.main.offer.BuyOfferView;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
@FxmlView
public class AccountSetupWizard extends ActivatableView implements Wizard {
@FXML VBox leftVBox;
@FXML AnchorPane content;
private WizardItem seedWords, password, fiatAccount, restrictions, registration;
private Navigation.Listener listener;
private final ViewLoader viewLoader;
private final Navigation navigation;
@Inject
private AccountSetupWizard(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
listener = viewPath -> {
if (viewPath.size() != 4 || !viewPath.contains(this.getClass()))
return;
Class<? extends View> viewClass = viewPath.tip();
if (viewClass == SeedWordsView.class) {
seedWords.show();
}
else if (viewClass == PasswordView.class) {
seedWords.onCompleted();
password.show();
}
else if (viewClass == RestrictionsView.class) {
seedWords.onCompleted();
password.onCompleted();
restrictions.show();
}
else if (viewClass == FiatAccountView.class) {
seedWords.onCompleted();
password.onCompleted();
restrictions.onCompleted();
fiatAccount.show();
}
else if (viewClass == RegistrationView.class) {
seedWords.onCompleted();
password.onCompleted();
restrictions.onCompleted();
fiatAccount.onCompleted();
registration.show();
}
};
seedWords = new WizardItem(SeedWordsView.class,
"Backup wallet seed", "Write down the seed word for your wallet");
password = new WizardItem(PasswordView.class,
"Setup password", "Protect your wallet with a password");
restrictions = new WizardItem(RestrictionsView.class,
"Select arbitrators", "Select which arbitrators you want to use for trading");
fiatAccount = new WizardItem(FiatAccountView.class,
" Setup Payments account(s)", "You need to setup at least one payment account");
registration = new WizardItem(RegistrationView.class,
"Register your account", "The registration in the Blockchain requires a payment of 0.0002 BTC");
leftVBox.getChildren().addAll(seedWords, password, restrictions, fiatAccount, registration);
}
@Override
public void activate() {
navigation.addListener(listener);
seedWords.show();
}
@Override
public void deactivate() {
navigation.removeListener(listener);
}
@Override
public void nextStep(Step currentStep) {
if (currentStep instanceof SeedWordsView) {
seedWords.onCompleted();
password.show();
}
else if (currentStep instanceof PasswordView) {
password.onCompleted();
restrictions.show();
}
else if (currentStep instanceof RestrictionsView) {
restrictions.onCompleted();
fiatAccount.show();
}
else if (currentStep instanceof FiatAccountView) {
fiatAccount.onCompleted();
registration.show();
}
else if (currentStep instanceof RegistrationView) {
registration.onCompleted();
if (navigation.getReturnPath() != null)
navigation.navigateTo(navigation.getReturnPath());
else
navigation.navigateTo(MainView.class, BuyOfferView.class);
}
}
protected void loadView(Class<? extends View> viewClass) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
if (view instanceof Wizard.Step)
((Step) view).setWizard(this);
}
private class WizardItem extends HBox {
private final ImageView imageView;
private final Label titleLabel;
private final Label subTitleLabel;
private final Class<? extends View> viewClass;
WizardItem(Class<? extends View> viewClass, String title, String subTitle) {
this.viewClass = viewClass;
setId("wizard-item-background-deactivated");
setSpacing(5);
setPrefWidth(200);
imageView = new ImageView();
imageView.setId("image-arrow-grey");
imageView.setFitHeight(15);
imageView.setFitWidth(20);
imageView.setPickOnBounds(true);
imageView.setMouseTransparent(true);
HBox.setMargin(imageView, new Insets(8, 0, 0, 8));
titleLabel = new Label(title);
titleLabel.setId("wizard-title-deactivated");
titleLabel.setLayoutX(7);
titleLabel.setMouseTransparent(true);
subTitleLabel = new Label(subTitle);
subTitleLabel.setId("wizard-sub-title-deactivated");
subTitleLabel.setLayoutX(40);
subTitleLabel.setLayoutY(33);
subTitleLabel.setMaxWidth(250);
subTitleLabel.setWrapText(true);
subTitleLabel.setMouseTransparent(true);
final VBox vBox = new VBox();
vBox.setSpacing(1);
HBox.setMargin(vBox, new Insets(5, 0, 8, 0));
vBox.setMouseTransparent(true);
vBox.getChildren().addAll(titleLabel, subTitleLabel);
getChildren().addAll(imageView, vBox);
}
void show() {
loadView(viewClass);
setId("wizard-item-background-active");
imageView.setId("image-arrow-blue");
titleLabel.setId("wizard-title-active");
subTitleLabel.setId("wizard-sub-title-active");
}
void onCompleted() {
setId("wizard-item-background-completed");
imageView.setId("image-tick");
titleLabel.setId("wizard-title-completed");
subTitleLabel.setId("wizard-sub-title-completed");
}
}
}

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import io.bitsquare.gui.components.TitledGroupBg?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.debug.DebugView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="10.0" left="25.0" top="30.0" right="25"/>
</padding>
<TitledGroupBg text="Intercept task" GridPane.rowSpan="8"/>
<Label text="Select Task:" GridPane.rowIndex="0">
<GridPane.margin>
<Insets top="10"/>
</GridPane.margin>
</Label>
<ComboBox fx:id="taskComboBox" onAction="#onSelectTask" promptText="Select task" GridPane.rowIndex="0" GridPane.columnIndex="1">
<GridPane.margin>
<Insets top="10"/>
</GridPane.margin>
</ComboBox>
<!-- <Label text="P2P network connection:" GridPane.rowIndex="1"/>
<TextField fx:id="connectionType" GridPane.rowIndex="1" GridPane.columnIndex="1"
mouseTransparent="true" focusTraversable="false"/>
<Label text="My external visible P2P network address:" GridPane.rowIndex="2"/>
<TextField fx:id="nodeAddress" GridPane.rowIndex="2" GridPane.columnIndex="1"
mouseTransparent="true" focusTraversable="false"/>
-->
<Label text="Intercept before run?:" GridPane.rowIndex="3">
<GridPane.margin>
<Insets bottom="-15"/>
</GridPane.margin>
</Label>
<CheckBox fx:id="interceptBeforeCheckBox" onAction="#onCheckBoxChanged" GridPane.rowIndex="3" GridPane.columnIndex="1">
<GridPane.margin>
<Insets bottom="-15"/>
</GridPane.margin>
</CheckBox>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="200.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
</rowConstraints>
</GridPane>

View file

@ -0,0 +1,179 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.debug;
import io.bitsquare.common.taskrunner.Task;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.InitializableView;
import io.bitsquare.trade.protocol.availability.CheckOfferAvailabilityProtocol;
import io.bitsquare.trade.protocol.availability.tasks.ProcessReportOfferAvailabilityMessage;
import io.bitsquare.trade.protocol.availability.tasks.RequestIsOfferAvailable;
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferProtocol;
import io.bitsquare.trade.protocol.placeoffer.tasks.AddOfferToRemoteOfferBook;
import io.bitsquare.trade.protocol.placeoffer.tasks.BroadcastCreateOfferFeeTx;
import io.bitsquare.trade.protocol.placeoffer.tasks.CreateOfferFeeTx;
import io.bitsquare.trade.protocol.placeoffer.tasks.ValidateOffer;
import io.bitsquare.trade.protocol.trade.BuyerAsOffererProtocol;
import io.bitsquare.trade.protocol.trade.SellerAsTakerProtocol;
import io.bitsquare.trade.protocol.trade.tasks.buyer.CreateAndSignPayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.buyer.CreateDepositTxInputs;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessPayoutTxFinalizedMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessRequestDepositTxInputsMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessRequestPublishDepositTxMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.SendDepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.SendFiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.SendRequestPayDepositMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.SignAndPublishDepositTx;
import io.bitsquare.trade.protocol.trade.tasks.offerer.VerifyTakeOfferFeePayment;
import io.bitsquare.trade.protocol.trade.tasks.offerer.VerifyTakerAccount;
import io.bitsquare.trade.protocol.trade.tasks.seller.CommitDepositTx;
import io.bitsquare.trade.protocol.trade.tasks.seller.CreateAndSignDepositTx;
import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessDepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessFiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessRequestPayDepositMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SendPayoutTxFinalizedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SendRequestDepositTxInputsMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SignAndFinalizePayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.taker.CreateTakeOfferFeeTx;
import io.bitsquare.trade.protocol.trade.tasks.taker.VerifyOfferFeePayment;
import io.bitsquare.trade.protocol.trade.tasks.taker.VerifyOffererAccount;
import java.util.Arrays;
import javax.inject.Inject;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.util.StringConverter;
@FxmlView
public class DebugView extends InitializableView {
@FXML ComboBox<Class> taskComboBox;
@FXML CheckBox interceptBeforeCheckBox;
@Inject
public DebugView() {
}
@Override
public void initialize() {
interceptBeforeCheckBox.setSelected(true);
final ObservableList<Class> items = FXCollections.observableArrayList(Arrays.asList(
/*---- Protocol ----*/
CheckOfferAvailabilityProtocol.class,
io.bitsquare.trade.protocol.availability.tasks.GetPeerAddress.class,
RequestIsOfferAvailable.class,
ProcessReportOfferAvailabilityMessage.class,
Boolean.class, /* used as seperator*/
/*---- Protocol ----*/
PlaceOfferProtocol.class,
ValidateOffer.class,
CreateOfferFeeTx.class,
AddOfferToRemoteOfferBook.class,
BroadcastCreateOfferFeeTx.class,
Boolean.class, /* used as seperator*/
/*---- Protocol ----*/
BuyerAsOffererProtocol.class,
ProcessRequestDepositTxInputsMessage.class,
CreateDepositTxInputs.class,
SendRequestPayDepositMessage.class,
ProcessRequestPublishDepositTxMessage.class,
VerifyTakerAccount.class,
SignAndPublishDepositTx.class,
SendDepositTxPublishedMessage.class,
CreateAndSignPayoutTx.class,
VerifyTakeOfferFeePayment.class,
SendFiatTransferStartedMessage.class,
ProcessPayoutTxFinalizedMessage.class,
Boolean.class, /* used as seperator*/
/*---- Protocol ----*/
SellerAsTakerProtocol.class,
CreateTakeOfferFeeTx.class,
SendRequestDepositTxInputsMessage.class,
ProcessRequestPayDepositMessage.class,
VerifyOffererAccount.class,
CreateAndSignDepositTx.class,
ProcessDepositTxPublishedMessage.class,
CommitDepositTx.class,
ProcessFiatTransferStartedMessage.class,
SignAndFinalizePayoutTx.class,
VerifyOfferFeePayment.class,
SendPayoutTxFinalizedMessage.class
)
);
taskComboBox.setVisibleRowCount(items.size());
taskComboBox.setItems(items);
taskComboBox.setConverter(new StringConverter<Class>() {
@Override
public String toString(Class item) {
if (item.getSimpleName().contains("Protocol"))
return "--- " + item.getSimpleName() + " ---";
else if (item.getSimpleName().contains("Boolean"))
return "";
else
return item.getSimpleName();
}
@Override
public Class fromString(String s) {
return null;
}
});
}
@FXML
void onSelectTask() {
Class item = taskComboBox.getSelectionModel().getSelectedItem();
if (!item.getSimpleName().contains("Protocol")) {
if (interceptBeforeCheckBox.isSelected()) {
Task.taskToInterceptBeforeRun = item;
Task.taskToInterceptAfterRun = null;
}
else {
Task.taskToInterceptAfterRun = item;
Task.taskToInterceptBeforeRun = null;
}
}
}
@FXML
void onCheckBoxChanged() {
onSelectTask();
}
}

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<TabPane fx:id="root" fx:controller="io.bitsquare.gui.main.funds.FundsView"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"
AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<Tab fx:id="reservedTab" text="Reserved for trades" closable="false"/>
<Tab fx:id="withdrawalTab" text="Open for withdrawal" closable="false"/>
<Tab fx:id="transactionsTab" text="Transactions" closable="false"/>
</TabPane>

View file

@ -0,0 +1,111 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.funds;
import io.bitsquare.common.model.Activatable;
import io.bitsquare.common.view.ActivatableViewAndModel;
import io.bitsquare.common.view.CachingViewLoader;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.common.view.View;
import io.bitsquare.common.view.ViewLoader;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.funds.reserved.ReservedView;
import io.bitsquare.gui.main.funds.transactions.TransactionsView;
import io.bitsquare.gui.main.funds.withdrawal.WithdrawalView;
import javax.inject.Inject;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.scene.control.*;
@FxmlView
public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
@FXML Tab reservedTab, withdrawalTab, transactionsTab;
private Navigation.Listener navigationListener;
private ChangeListener<Tab> tabChangeListener;
private Tab currentTab;
private final ViewLoader viewLoader;
private final Navigation navigation;
@Inject
public FundsView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
navigationListener = viewPath -> {
if (viewPath.size() == 3 && viewPath.indexOf(FundsView.class) == 1)
loadView(viewPath.tip());
};
tabChangeListener = (ov, oldValue, newValue) -> {
if (newValue == reservedTab)
navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class);
else if (newValue == withdrawalTab)
navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class);
else if (newValue == transactionsTab)
navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class);
};
}
@Override
public void doActivate() {
root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener);
navigation.addListener(navigationListener);
if (root.getSelectionModel().getSelectedItem() == reservedTab)
navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class);
else if (root.getSelectionModel().getSelectedItem() == withdrawalTab)
navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class);
else if (root.getSelectionModel().getSelectedItem() == transactionsTab)
navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class);
}
@Override
public void doDeactivate() {
root.getSelectionModel().selectedItemProperty().removeListener(tabChangeListener);
navigation.removeListener(navigationListener);
currentTab = null;
}
private void loadView(Class<? extends View> viewClass) {
// we want to get activate/deactivate called, so we remove the old view on tab change
if (currentTab != null)
currentTab.setContent(null);
View view = viewLoader.load(viewClass);
if (view instanceof ReservedView)
currentTab = reservedTab;
else if (view instanceof WithdrawalView)
currentTab = withdrawalTab;
else if (view instanceof TransactionsView)
currentTab = transactionsTab;
currentTab.setContent(view.getRoot());
root.getSelectionModel().select(currentTab);
}
}

View file

@ -0,0 +1,170 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.funds.reserved;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.AddressConfidenceListener;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
import io.bitsquare.gui.util.BSFormatter;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.TransactionConfidence;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.*;
import static com.google.common.base.Preconditions.checkNotNull;
public class ReservedListItem {
private final StringProperty addressString = new SimpleStringProperty();
private final BalanceListener balanceListener;
private final Label balanceLabel;
private final AddressEntry addressEntry;
private final WalletService walletService;
private final BSFormatter formatter;
private final AddressConfidenceListener confidenceListener;
private final ConfidenceProgressIndicator progressIndicator;
private final Tooltip tooltip;
private Coin balance;
public ReservedListItem(AddressEntry addressEntry, WalletService walletService, BSFormatter formatter) {
this.addressEntry = addressEntry;
this.walletService = walletService;
this.formatter = formatter;
this.addressString.set(getAddress().toString());
// confidence
progressIndicator = new ConfidenceProgressIndicator();
progressIndicator.setId("funds-confidence");
tooltip = new Tooltip("Not used yet");
progressIndicator.setProgress(0);
progressIndicator.setPrefSize(24, 24);
Tooltip.install(progressIndicator, tooltip);
confidenceListener = walletService.addAddressConfidenceListener(new AddressConfidenceListener(getAddress()) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
updateConfidence(confidence);
}
});
updateConfidence(walletService.getConfidenceForAddress(getAddress()));
// balance
balanceLabel = new Label();
balanceListener = walletService.addBalanceListener(new BalanceListener(getAddress()) {
@Override
public void onBalanceChanged(Coin balance) {
updateBalance(balance);
}
});
updateBalance(walletService.getBalanceForAddress(getAddress()));
}
public void cleanup() {
walletService.removeAddressConfidenceListener(confidenceListener);
walletService.removeBalanceListener(balanceListener);
}
private void updateBalance(Coin balance) {
this.balance = balance;
if (balance != null) {
balanceLabel.setText(formatter.formatCoin(balance));
}
}
private void updateConfidence(TransactionConfidence confidence) {
if (confidence != null) {
//log.debug("Type numBroadcastPeers getDepthInBlocks " + confidence.getConfidenceType() + " / " +
// confidence.numBroadcastPeers() + " / " + confidence.getDepthInBlocks());
switch (confidence.getConfidenceType()) {
case UNKNOWN:
tooltip.setText("Unknown transaction status");
progressIndicator.setProgress(0);
break;
case PENDING:
tooltip.setText("Seen by " + confidence.numBroadcastPeers() + " peer(s) / 0 confirmations");
progressIndicator.setProgress(-1.0);
break;
case BUILDING:
tooltip.setText("Confirmed in " + confidence.getDepthInBlocks() + " block(s)");
progressIndicator.setProgress(Math.min(1, (double) confidence.getDepthInBlocks() / 6.0));
break;
case DEAD:
tooltip.setText("Transaction is invalid.");
progressIndicator.setProgress(0);
break;
}
}
}
public final String getLabel() {
switch (addressEntry.getContext()) {
case REGISTRATION_FEE:
return "Registration fee";
case TRADE:
checkNotNull(addressEntry.getOfferId());
return "Offer ID: " + addressEntry.getOfferId();
case ARBITRATOR_DEPOSIT:
return "Arbitration deposit";
}
return "";
}
public final StringProperty addressStringProperty() {
return this.addressString;
}
Address getAddress() {
return addressEntry.getAddress();
}
public AddressEntry getAddressEntry() {
return addressEntry;
}
public ConfidenceProgressIndicator getProgressIndicator() {
return progressIndicator;
}
public Label getBalanceLabel() {
return balanceLabel;
}
public Coin getBalance() {
return balance;
}
}

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.cell.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.reserved.ReservedView"
spacing="10" xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS">
<columns>
<TableColumn text="Label" fx:id="labelColumn" minWidth="100" sortable="false"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="240" sortable="false">
<cellValueFactory>
<PropertyValueFactory property="addressString"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Balance" fx:id="balanceColumn" minWidth="50" sortable="false"/>
<TableColumn text="Copy" fx:id="copyColumn" minWidth="30" sortable="false"/>
<TableColumn text="Status" fx:id="confidenceColumn" minWidth="30" sortable="false"/>
</columns>
</TableView>
</VBox>

View file

@ -0,0 +1,234 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.funds.reserved;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.common.view.ActivatableViewAndModel;
import io.bitsquare.common.view.FxmlView;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.util.Utilities;
import org.bitcoinj.core.Coin;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.util.Callback;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
@FxmlView
public class ReservedView extends ActivatableViewAndModel {
@FXML TableView<ReservedListItem> table;
@FXML TableColumn<ReservedListItem, ReservedListItem> labelColumn, addressColumn, balanceColumn, copyColumn,
confidenceColumn;
private final WalletService walletService;
private final TradeManager tradeManager;
private final BSFormatter formatter;
private final ObservableList<ReservedListItem> addressList = FXCollections.observableArrayList();
@Inject
private ReservedView(WalletService walletService, TradeManager tradeManager, BSFormatter formatter) {
this.walletService = walletService;
this.tradeManager = tradeManager;
this.formatter = formatter;
}
@Override
public void initialize() {
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No funded are reserved in open offers or trades"));
setLabelColumnCellFactory();
setBalanceColumnCellFactory();
setCopyColumnCellFactory();
setConfidenceColumnCellFactory();
}
@Override
public void doActivate() {
fillList();
table.setItems(addressList);
walletService.addBalanceListener(new BalanceListener() {
@Override
public void onBalanceChanged(Coin balance) {
fillList();
}
});
}
@Override
public void doDeactivate() {
addressList.forEach(ReservedListItem::cleanup);
}
private void fillList() {
addressList.clear();
addressList.addAll(Stream.concat(tradeManager.getOpenOfferTrades().stream(), tradeManager.getPendingTrades().stream())
.map(trade -> new ReservedListItem(walletService.getAddressEntry(trade.getId()), walletService, formatter))
.collect(Collectors.toList()));
// List<AddressEntry> addressEntryList = walletService.getAddressEntryList();
/* addressList.addAll(addressEntryList.stream()
.filter(e -> walletService.getBalanceForAddress(e.getAddress()).isPositive())
.map(anAddressEntryList -> new ReservedListItem(anAddressEntryList, walletService, formatter))
.collect(Collectors.toList()));*/
}
private void setLabelColumnCellFactory() {
labelColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
labelColumn.setCellFactory(new Callback<TableColumn<ReservedListItem, ReservedListItem>,
TableCell<ReservedListItem,
ReservedListItem>>() {
@Override
public TableCell<ReservedListItem, ReservedListItem> call(TableColumn<ReservedListItem,
ReservedListItem> column) {
return new TableCell<ReservedListItem, ReservedListItem>() {
private Hyperlink hyperlink;
@Override
public void updateItem(final ReservedListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
hyperlink = new Hyperlink(item.getLabel());
hyperlink.setId("id-link");
if (item.getAddressEntry().getOfferId() != null) {
Tooltip tooltip = new Tooltip(item.getAddressEntry().getOfferId());
Tooltip.install(hyperlink, tooltip);
hyperlink.setOnAction(event -> log.info("Show trade details " + item.getAddressEntry
().getOfferId()));
}
setGraphic(hyperlink);
}
else {
setGraphic(null);
setId(null);
}
}
};
}
});
}
private void setBalanceColumnCellFactory() {
balanceColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
balanceColumn.setCellFactory(
new Callback<TableColumn<ReservedListItem, ReservedListItem>, TableCell<ReservedListItem,
ReservedListItem>>() {
@Override
public TableCell<ReservedListItem, ReservedListItem> call(TableColumn<ReservedListItem,
ReservedListItem> column) {
return new TableCell<ReservedListItem, ReservedListItem>() {
@Override
public void updateItem(final ReservedListItem item, boolean empty) {
super.updateItem(item, empty);
setGraphic((item != null && !empty) ? item.getBalanceLabel() : null);
}
};
}
});
}
private void setCopyColumnCellFactory() {
copyColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
copyColumn.setCellFactory(
new Callback<TableColumn<ReservedListItem, ReservedListItem>, TableCell<ReservedListItem,
ReservedListItem>>() {
@Override
public TableCell<ReservedListItem, ReservedListItem> call(TableColumn<ReservedListItem,
ReservedListItem> column) {
return new TableCell<ReservedListItem, ReservedListItem>() {
final Label copyIcon = new Label();
{
copyIcon.getStyleClass().add("copy-icon");
AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY);
Tooltip.install(copyIcon, new Tooltip("Copy address to clipboard"));
}
@Override
public void updateItem(final ReservedListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setGraphic(copyIcon);
copyIcon.setOnMouseClicked(e -> Utilities.copyToClipboard(item
.addressStringProperty().get()));
}
else {
setGraphic(null);
}
}
};
}
});
}
private void setConfidenceColumnCellFactory() {
confidenceColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
confidenceColumn.setCellFactory(
new Callback<TableColumn<ReservedListItem, ReservedListItem>, TableCell<ReservedListItem,
ReservedListItem>>() {
@Override
public TableCell<ReservedListItem, ReservedListItem> call(TableColumn<ReservedListItem,
ReservedListItem> column) {
return new TableCell<ReservedListItem, ReservedListItem>() {
@Override
public void updateItem(final ReservedListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setGraphic(item.getProgressIndicator());
}
else {
setGraphic(null);
}
}
};
}
});
}
}

Some files were not shown because too many files have changed in this diff Show more