save transaction diagram as image through context menu on transaction label

This commit is contained in:
Craig Raw 2022-09-16 12:12:25 +02:00
parent 923c61fceb
commit da3399468c
3 changed files with 85 additions and 19 deletions

View file

@ -21,6 +21,7 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
@ -28,6 +29,10 @@ import javafx.scene.Group;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.input.Clipboard; import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent; import javafx.scene.input.ClipboardContent;
import javafx.scene.input.MouseButton; import javafx.scene.input.MouseButton;
@ -37,6 +42,7 @@ import javafx.scene.paint.Color;
import javafx.scene.shape.Circle; import javafx.scene.shape.Circle;
import javafx.scene.shape.CubicCurve; import javafx.scene.shape.CubicCurve;
import javafx.scene.shape.Line; import javafx.scene.shape.Line;
import javafx.stage.FileChooser;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.StageStyle; import javafx.stage.StageStyle;
@ -45,7 +51,12 @@ import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.Glyph; import org.controlsfx.glyphfont.Glyph;
import org.controlsfx.tools.Platform; import org.controlsfx.tools.Platform;
import javax.imageio.ImageIO;
import java.awt.image.*;
import java.io.File;
import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.sparrowwallet.sparrow.control.CoinLabel.BTC_FORMAT; import static com.sparrowwallet.sparrow.control.CoinLabel.BTC_FORMAT;
@ -75,7 +86,7 @@ public class TransactionDiagram extends GridPane {
public void handle(MouseEvent event) { public void handle(MouseEvent event) {
if(!event.isConsumed() && event.getButton() != MouseButton.SECONDARY) { if(!event.isConsumed() && event.getButton() != MouseButton.SECONDARY) {
Stage stage = new Stage(StageStyle.UNDECORATED); Stage stage = new Stage(StageStyle.UNDECORATED);
stage.setTitle(walletTx.getPayments().iterator().next().getLabel()); stage.setTitle(getDiagramTitle());
stage.initOwner(TransactionDiagram.this.getScene().getWindow()); stage.initOwner(TransactionDiagram.this.getScene().getWindow());
stage.initModality(Modality.WINDOW_MODAL); stage.initModality(Modality.WINDOW_MODAL);
stage.setResizable(false); stage.setResizable(false);
@ -98,7 +109,8 @@ public class TransactionDiagram extends GridPane {
expandedDiagram = new TransactionDiagram(); expandedDiagram = new TransactionDiagram();
expandedDiagram.setId("transactionDiagram"); expandedDiagram.setId("transactionDiagram");
expandedDiagram.setExpanded(true); expandedDiagram.setExpanded(true);
updateExpandedDiagram(); expandedDiagram.setFinal(isFinal());
updateDerivedDiagram(expandedDiagram);
HBox buttonBox = new HBox(); HBox buttonBox = new HBox();
buttonBox.setAlignment(Pos.CENTER_RIGHT); buttonBox.setAlignment(Pos.CENTER_RIGHT);
@ -136,7 +148,7 @@ public class TransactionDiagram extends GridPane {
update(); update();
setOnMouseClicked(expandedDiagramHandler); setOnMouseClicked(expandedDiagramHandler);
if(expandedDiagram != null) { if(expandedDiagram != null) {
updateExpandedDiagram(); updateDerivedDiagram(expandedDiagram);
} }
} }
} }
@ -165,20 +177,22 @@ public class TransactionDiagram extends GridPane {
getChildren().clear(); getChildren().clear();
} }
private void updateExpandedDiagram() { private void updateDerivedDiagram(TransactionDiagram diagram) {
expandedDiagram.setFinal(isFinal()); diagram.setOptimizationStrategy(getOptimizationStrategy());
expandedDiagram.setOptimizationStrategy(getOptimizationStrategy()); diagram.walletTx = walletTx;
expandedDiagram.walletTx = walletTx;
List<Map<BlockTransactionHashIndex, WalletNode>> utxoSets = expandedDiagram.getDisplayedUtxoSets(); if(diagram.isExpanded()) {
int maxSetSize = utxoSets.stream().mapToInt(Map::size).max().orElse(0); List<Map<BlockTransactionHashIndex, WalletNode>> utxoSets = diagram.getDisplayedUtxoSets();
int maxRows = Math.max(maxSetSize * utxoSets.size(), walletTx.getPayments().size() + 2); int maxSetSize = utxoSets.stream().mapToInt(Map::size).max().orElse(0);
double diagramHeight = Math.max(DIAGRAM_HEIGHT, Math.min(EXPANDED_DIAGRAM_HEIGHT, maxRows * ROW_HEIGHT)); int maxRows = Math.max(maxSetSize * utxoSets.size(), walletTx.getPayments().size() + 2);
expandedDiagram.setMinHeight(diagramHeight); double diagramHeight = Math.max(DIAGRAM_HEIGHT, Math.min(EXPANDED_DIAGRAM_HEIGHT, maxRows * ROW_HEIGHT));
expandedDiagram.setMaxHeight(diagramHeight); diagram.setMinHeight(diagramHeight);
expandedDiagram.update(); diagram.setMaxHeight(diagramHeight);
}
if(expandedDiagram.getScene() != null && expandedDiagram.getScene().getWindow() instanceof Stage stage) { diagram.update();
if(diagram.getScene() != null && diagram.getScene().getWindow() instanceof Stage stage) {
stage.sizeToScene(); stage.sizeToScene();
} }
} }
@ -797,12 +811,59 @@ public class TransactionDiagram extends GridPane {
tooltip.setShowDuration(Duration.INDEFINITE); tooltip.setShowDuration(Duration.INDEFINITE);
tooltip.getStyleClass().add("transaction-tooltip"); tooltip.getStyleClass().add("transaction-tooltip");
txLabel.setTooltip(tooltip); txLabel.setTooltip(tooltip);
ContextMenu contextMenu = new ContextMenu();
MenuItem menuItem = new MenuItem("Save as Image...");
menuItem.setOnAction(event -> {
contextMenu.hide();
saveAsImage();
});
contextMenu.getItems().add(menuItem);
txLabel.setContextMenu(contextMenu);
txPane.getChildren().add(txLabel); txPane.getChildren().add(txLabel);
txPane.getChildren().add(createSpacer()); txPane.getChildren().add(createSpacer());
return txPane; return txPane;
} }
private void saveAsImage() {
Stage window = new Stage();
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Save Image");
fileChooser.setInitialFileName(getDiagramTitle() + ".png");
AppServices.moveToActiveWindowScreen(window, 800, 450);
File file = fileChooser.showSaveDialog(window);
if(file != null) {
TransactionDiagram transactionDiagram = new TransactionDiagram();
transactionDiagram.setId("transactionDiagram");
transactionDiagram.setFinal(true);
transactionDiagram.setExpanded(isExpanded());
transactionDiagram.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));
transactionDiagram.setStyle("-fx-text-background-color: #000000");
updateDerivedDiagram(transactionDiagram);
Scene scene = new Scene(transactionDiagram);
scene.setFill(Color.TRANSPARENT);
scene.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
scene.getStylesheets().add(AppServices.class.getResource("wallet/wallet.css").toExternalForm());
scene.getStylesheets().add(AppServices.class.getResource("wallet/send.css").toExternalForm());
Image image = scene.snapshot(null);
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
try {
ImageIO.write(bufferedImage, "png", file);
} catch(IOException e) {
AppServices.showErrorDialog("Error saving image", e.getMessage());
}
}
}
private String getDiagramTitle() {
if(!isFinal() && walletTx.getPayments().size() > 0 && walletTx.getPayments().get(0).getLabel() != null) {
return walletTx.getPayments().get(0).getLabel();
} else {
return "[" + walletTx.getTransaction().getTxId().toString().substring(0, 6) + "]";
}
}
public double getDiagramHeight() { public double getDiagramHeight() {
if(isExpanded()) { if(isExpanded()) {
return getMaxHeight(); return getMaxHeight();

View file

@ -206,7 +206,7 @@
-fx-background-color: transparent; -fx-background-color: transparent;
} }
.root #transactionDiagram .coins-icon, #transactionDiagram .user-icon { .root #transactionDiagram .coins-icon, .root #transactionDiagram .user-icon {
-fx-text-fill: lightgray; -fx-text-fill: lightgray;
} }
@ -214,6 +214,11 @@
-fx-fill: lightgray; -fx-fill: lightgray;
} }
.root #transactionDiagram .inputs-type, .root #transactionDiagram .input-line, .root #transactionDiagram .output-line {
-fx-text-fill: #696c77;
-fx-stroke: #696c77;
}
.root .progress-indicator.progress-timer.warn > .determinate-indicator > .indicator { .root .progress-indicator.progress-timer.warn > .determinate-indicator > .indicator {
-fx-background-color: -fx-box-border, radial-gradient(center 50% 50%, radius 50%, #e06c75 70%, derive(-fx-control-inner-background, -9%) 100%); -fx-background-color: -fx-box-border, radial-gradient(center 50% 50%, radius 50%, #e06c75 70%, derive(-fx-control-inner-background, -9%) 100%);
} }

View file

@ -80,13 +80,13 @@
#transactionDiagram .inputs-type, #transactionDiagram .input-line, #transactionDiagram .output-line { #transactionDiagram .inputs-type, #transactionDiagram .input-line, #transactionDiagram .output-line {
-fx-fill: transparent; -fx-fill: transparent;
-fx-text-fill: #696c77; -fx-text-fill: -fx-text-background-color;
-fx-stroke: #696c77; -fx-stroke: -fx-text-background-color;
-fx-stroke-width: 1px; -fx-stroke-width: 1px;
} }
#transactionDiagram .size-indicator { #transactionDiagram .size-indicator {
-fx-fill: -fx-text-base-color; -fx-fill: -fx-text-background-color;
} }
#transactionDiagram .input-dashed-line { #transactionDiagram .input-dashed-line {