Add peer info popup with tag editor and tag/nr. of trades icon

This commit is contained in:
Manfred Karrer 2016-06-17 01:40:14 +02:00
parent b7821da7a3
commit 212791192f
9 changed files with 347 additions and 19 deletions

View File

@ -117,6 +117,7 @@ public final class Preferences implements Persistable {
private boolean useInvertedMarketPrice;
private boolean useStickyMarketPrice = false;
private boolean usePercentageBasedPrice = false;
private Map<String, String> peerTagMap = new HashMap<>();
// Observable wrappers
transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination);
@ -179,6 +180,9 @@ public final class Preferences implements Persistable {
} catch (Exception e) {
// leave default value
}
if (persisted.getPeerTagMap() != null)
peerTagMap = persisted.getPeerTagMap();
} else {
setFiatCurrencies(CurrencyUtil.getAllMainFiatCurrencies());
fiatCurrencies = new ArrayList<>(fiatCurrenciesAsObservable);
@ -379,6 +383,11 @@ public final class Preferences implements Persistable {
storage.queueUpForSave();
}
public void setTagForPeer(String hostName, String tag) {
peerTagMap.put(hostName, tag);
storage.queueUpForSave();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getter
@ -504,6 +513,10 @@ public final class Preferences implements Persistable {
return usePercentageBasedPrice;
}
public Map<String, String> getPeerTagMap() {
return peerTagMap;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private

View File

@ -1020,6 +1020,22 @@ textfield */
-fx-effect: dropshadow(gaussian, #666, 12, 0, -1, 3);
}
#peer-info-popup-bg {
-fx-font-size: 11;
-fx-text-fill: #3c3c3c;
-fx-background-color: linear-gradient(to bottom, #fafafa, #f1f1f1);
-fx-background-radius: 3 3 3 3;
-fx-background-insets: 5 5 20 20;
-fx-effect: dropshadow(gaussian, #666, 12, 0, -1, 3);
}
#peer-info-popup-headline {
-fx-font-size: 13;
-fx-font-weight: bold;
-fx-text-fill: #333;
}
.popup-icon-information {
-fx-text-fill: #ddd;
}

View File

@ -26,6 +26,10 @@
-fx-image: url("../../../images/green_circle.png");
}
#image-blue_circle {
-fx-image: url("../../../images/blue_circle.png");
}
#image-remove {
-fx-image: url("../../../images/remove.png");
}

View File

@ -152,7 +152,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
if (DevFlags.DEV_MODE) {
amount.set("0.0001");
minAmount.set(amount.get());
price.set("400");
price.set("700");
volume.set("0.04");
setAmountToModel();

View File

@ -123,7 +123,7 @@ public abstract class Overlay<T extends Overlay> {
protected String truncatedMessage;
private StaticProgressIndicator progressIndicator;
private boolean showProgressIndicator;
private Button actionButton;
protected Button actionButton;
protected Label headLineLabel;
protected String dontShowAgainId;
protected String dontShowAgainText;

View File

@ -0,0 +1,250 @@
package io.bitsquare.gui.main.overlays.editor;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.main.overlays.Overlay;
import io.bitsquare.gui.util.FormBuilder;
import io.bitsquare.user.Preferences;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Camera;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.GridPane;
import javafx.scene.transform.Rotate;
import javafx.stage.Modality;
import javafx.stage.Window;
import javafx.util.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.function.Consumer;
public class PeerInfoWithTagEditor extends Overlay<PeerInfoWithTagEditor> {
private static final Logger log = LoggerFactory.getLogger(PeerInfoWithTagEditor.class);
private InputTextField inputTextField;
private Point2D position;
private static PeerInfoWithTagEditor INSTANCE;
private Consumer<String> saveHandler;
private String hostName;
private int numTrades;
private ChangeListener<Boolean> focusListener;
public PeerInfoWithTagEditor() {
width = 400;
type = Type.Undefined;
if (INSTANCE != null)
INSTANCE.hide();
INSTANCE = this;
}
public PeerInfoWithTagEditor onSave(Consumer<String> saveHandler) {
this.saveHandler = saveHandler;
return this;
}
public PeerInfoWithTagEditor position(Point2D position) {
this.position = position;
return this;
}
public PeerInfoWithTagEditor hostName(String hostName) {
this.hostName = hostName;
return this;
}
public PeerInfoWithTagEditor numTrades(int numTrades) {
this.numTrades = numTrades;
return this;
}
@Override
public void show() {
headLine("Peer info");
actionButtonText("Save");
createGridPane();
addHeadLine();
addContent();
addCloseButton();
applyStyles();
onShow();
}
@Override
protected void onShow() {
super.display();
if (stage != null) {
focusListener = (observable, oldValue, newValue) -> {
if (!newValue)
hide();
};
stage.focusedProperty().addListener(focusListener);
}
}
@Override
public void hide() {
animateHide(() -> {
removeEffectFromBackground();
if (stage != null)
stage.hide();
else
log.warn("Stage is null");
cleanup();
onHidden();
});
}
@Override
protected void onHidden() {
INSTANCE = null;
if (stage != null && focusListener != null)
stage.focusedProperty().removeListener(focusListener);
}
protected void addContent() {
FormBuilder.addLabelTextField(gridPane, ++rowIndex, "Onion address:", hostName);
FormBuilder.addLabelTextField(gridPane, ++rowIndex, "Number of completed trades:", String.valueOf(numTrades));
inputTextField = FormBuilder.addLabelInputTextField(gridPane, ++rowIndex, "Set tag for that peer:").second;
HashMap<String, String> peerTagMap = Preferences.INSTANCE.getPeerTagMap();
String tag = peerTagMap.containsKey(hostName) ? peerTagMap.get(hostName) : "";
inputTextField.setText(tag);
}
@Override
protected void addHeadLine() {
super.addHeadLine();
GridPane.setHalignment(headLineLabel, HPos.CENTER);
}
protected void setupKeyHandler(Scene scene) {
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ESCAPE) {
e.consume();
doClose();
}
if (e.getCode() == KeyCode.ENTER) {
e.consume();
save();
}
});
}
@Override
protected void animateHide(Runnable onFinishedHandler) {
if (Preferences.INSTANCE.getUseAnimations()) {
double duration = getDuration(300);
Interpolator interpolator = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
gridPane.setRotationAxis(Rotate.X_AXIS);
Camera camera = gridPane.getScene().getCamera();
gridPane.getScene().setCamera(new PerspectiveCamera());
Timeline timeline = new Timeline();
ObservableList<KeyFrame> keyFrames = timeline.getKeyFrames();
keyFrames.add(new KeyFrame(Duration.millis(0),
new KeyValue(gridPane.rotateProperty(), 0, interpolator),
new KeyValue(gridPane.opacityProperty(), 1, interpolator)
));
keyFrames.add(new KeyFrame(Duration.millis(duration),
new KeyValue(gridPane.rotateProperty(), -90, interpolator),
new KeyValue(gridPane.opacityProperty(), 0, interpolator)
));
timeline.setOnFinished(event -> {
gridPane.setRotate(0);
gridPane.setRotationAxis(Rotate.Z_AXIS);
gridPane.getScene().setCamera(camera);
onFinishedHandler.run();
});
timeline.play();
} else {
onFinishedHandler.run();
}
}
@Override
protected void animateDisplay() {
if (Preferences.INSTANCE.getUseAnimations()) {
double startY = -160;
double duration = getDuration(400);
Interpolator interpolator = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
double height = gridPane.getPrefHeight();
Timeline timeline = new Timeline();
ObservableList<KeyFrame> keyFrames = timeline.getKeyFrames();
keyFrames.add(new KeyFrame(Duration.millis(0),
new KeyValue(gridPane.opacityProperty(), 0, interpolator),
new KeyValue(gridPane.translateYProperty(), startY, interpolator)
));
keyFrames.add(new KeyFrame(Duration.millis(duration),
new KeyValue(gridPane.opacityProperty(), 1, interpolator),
new KeyValue(gridPane.translateYProperty(), 0, interpolator)
));
timeline.play();
}
}
@Override
protected void createGridPane() {
super.createGridPane();
gridPane.setPadding(new Insets(15, 15, 30, 30));
}
@Override
protected void addCloseButton() {
buttonDistance = 10;
super.addCloseButton();
actionButton.setOnAction(event -> save());
}
private void save() {
hide();
if (saveHandler != null && inputTextField != null)
saveHandler.accept(inputTextField.getText());
}
@Override
protected void applyStyles() {
gridPane.setId("peer-info-popup-bg");
if (headLineLabel != null)
headLineLabel.setId("peer-info-popup-headline");
}
@Override
protected void setModality() {
stage.initOwner(owner.getScene().getWindow());
stage.initModality(Modality.NONE);
}
@Override
protected void layout() {
Window window = owner.getScene().getWindow();
stage.setX(Math.round(window.getX() + position.getX() - width));
stage.setY(Math.round(window.getY() + position.getY()));
}
@Override
protected void addEffectToBackground() {
}
@Override
protected void removeEffectFromBackground() {
}
}

View File

@ -18,7 +18,10 @@
package io.bitsquare.gui.util;
import com.sun.javafx.tk.quantum.QuantumToolkit;
import io.bitsquare.gui.main.overlays.editor.PeerInfoWithTagEditor;
import io.bitsquare.locale.Country;
import io.bitsquare.user.Preferences;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
@ -34,6 +37,7 @@ import org.slf4j.LoggerFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.UUID;
@ -73,7 +77,7 @@ public class ImageUtil {
return isRetina;
}
public static Node getIdentIcon(String hostName, String tooltipText, int numPastTrades) {
public static Node getIdentIcon(String hostName, String tooltipText, int numTrades) {
if (!hostName.isEmpty()) {
// for testing locally we use a random hostname to get dif. colors
if (hostName.startsWith("localhost"))
@ -89,40 +93,65 @@ public class ImageUtil {
int index = (intValue % maxIndices) + 1;
double saturation = (intValue % 1000) / 1000d;
int red = (intValue >> 8) % 256;
int green = (intValue >> 16) % 64; // we use green for marking repeated trades, so avoid it in main bg color
int green = (intValue >> 16) % 256;
int blue = (intValue >> 24) % 256;
ImageView iconView = new ImageView();
iconView.setId("avatar_" + index);
iconView.setScaleX(intValue % 2 == 0 ? 1d : -1d);
double size = 26;
Group iconGroup = new Group();
Group group = new Group();
Map<String, String> peerTagMap = Preferences.INSTANCE.getPeerTagMap();
String tag;
if (peerTagMap.containsKey(hostName)) {
tag = peerTagMap.get(hostName);
Tooltip.install(group, new Tooltip(tooltipText + "\nTag: " + tag));
} else {
tag = "";
Tooltip.install(group, new Tooltip(tooltipText));
}
Pane numTradesPane = new Pane();
numTradesPane.relocate(16, 16);
numTradesPane.setMouseTransparent(true);
if (numPastTrades > 0) {
Label label = new Label(numPastTrades < 10 ? String.valueOf(numPastTrades) : "");
label.relocate(5, 1);
label.setId("ident-num-label");
ImageView icon = new ImageView();
icon.setLayoutX(0.5);
icon.setId("image-green_circle");
numTradesPane.getChildren().addAll(icon, label);
}
ImageView icon = new ImageView();
Label label = new Label();
label.relocate(5, 1);
label.setId("ident-num-label");
icon.setLayoutX(0.5);
numTradesPane.getChildren().addAll(icon, label);
Color color = Color.rgb(red, green, blue);
color = color.deriveColor(1, saturation, 1, 1); // reduce saturation
updatePeerInfoIcon(numTrades, tag, numTradesPane, label, icon);
Canvas bg = new Canvas(size, size);
GraphicsContext gc = bg.getGraphicsContext2D();
Color color = Color.rgb(red, green, blue);
color = color.deriveColor(1, saturation, 1, 1); // reduce saturation
gc.setFill(color);
gc.fillOval(0, 0, size, size);
bg.setLayoutY(1);
iconGroup.getChildren().addAll(bg, iconView, numTradesPane);
group.getChildren().addAll(bg, iconView, numTradesPane);
Tooltip.install(iconGroup, new Tooltip(tooltipText));
return iconGroup;
final String finalHostName = hostName;
group.setOnMouseClicked(e -> {
new PeerInfoWithTagEditor()
.hostName(finalHostName)
.numTrades(numTrades)
.position(group.localToScene(new Point2D(0, 0)))
.onSave(newTag -> {
Preferences.INSTANCE.setTagForPeer(finalHostName, newTag);
if (!newTag.isEmpty())
Tooltip.install(group, new Tooltip(tooltipText + "\nTag: " + newTag));
else
Tooltip.install(group, new Tooltip(tooltipText));
updatePeerInfoIcon(numTrades, newTag, numTradesPane, label, icon);
})
.show();
});
return group;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
log.error(e.toString());
@ -132,4 +161,20 @@ public class ImageUtil {
return null;
}
}
private static void updatePeerInfoIcon(int numTrades, String tag, Pane numTradesPane, Label label, ImageView icon) {
if (numTrades == 0 && tag.isEmpty())
label.setText("");
else if (numTrades == 0)
label.setText(tag.substring(0, 1));
else if (numTrades > 9)
label.setText("");
else
label.setText(String.valueOf(numTrades));
numTradesPane.setVisible(!label.getText().isEmpty());
numTradesPane.setManaged(numTradesPane.isVisible());
icon.setId(numTrades > 0 ? "image-green_circle" : "image-blue_circle");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB