// Copyright © 2016-2020 Andy Goryachev <[email protected]> package goryachev.fx; import goryachev.common.util.CComparator; import goryachev.common.util.CKit; import goryachev.common.util.CList; import goryachev.common.util.D; import goryachev.common.util.SB; import goryachev.common.util.TextTools; import javafx.css.CssMetaData; import javafx.css.PseudoClass; import javafx.css.Styleable; import javafx.scene.Node; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; import javafx.scene.input.PickResult; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.stage.Window; /** * Diagnostic tool to dump FX style information with a key press. */ public class FxDump { private final KeyCode trigger; private boolean showNulls; private double x; private double y; private PickResult r; private CComparator<CssMetaData<? extends Styleable,?>> sorter; public FxDump(KeyCode trigger) { this.trigger = trigger; } public FxDump() { this(KeyCode.BACK_QUOTE); } /** a shortcut to attach a default FxDump */ public static void attach(Node n) { new FxDump().attachNode(n); } /** a shortcut to attach a default FxDump */ public static void attach(Window n) { new FxDump().attachWindow(n); } /** controls whether to show null properties */ public void setShowNulls(boolean on) { showNulls = on; } public void attachWindow(Window s) { attachNode(s.getScene().getRoot()); } public void attachNode(Node n) { // I don't know how to listen to global mouse or key events in FX... n.getScene().addEventFilter(KeyEvent.KEY_PRESSED, (ev) -> handleKeyPress(ev)); n.getScene().addEventFilter(MouseEvent.ANY, (ev) -> handleMouseEvent(ev)); } protected void handleMouseEvent(MouseEvent ev) { x = ev.getScreenX(); y = ev.getScreenY(); r = ev.getPickResult(); } protected void handleKeyPress(KeyEvent ev) { if(ev.getCode() == trigger) { dump(); } } protected void dump() { if(r != null) { Node n = r.getIntersectedNode(); if(n != null) { dump(n); } } } protected void sort(CList<CssMetaData<? extends Styleable,?>> list) { if(sorter == null) { sorter = new CComparator<CssMetaData<? extends Styleable,?>>() { public int compare(CssMetaData<? extends Styleable,?> a, CssMetaData<? extends Styleable,?> b) { return compareAsStrings(a.getProperty(), b.getProperty()); } }; } sorter.sort(list); } protected boolean shouldShow(Object x) { if(x == null) { return showNulls; } return true; } protected String describeBackground(Background b) { SB sb = new SB(); sb.a("Background<"); boolean sep = false; for(BackgroundFill f: b.getFills()) { if(sep) { sb.a(","); } else { sep = true; } sb.a(describe(f)); } sb.a(">"); return sb.toString(); } protected String describeBackgroundFill(BackgroundFill f) { SB sb = new SB(); sb.a("Fill<").a(describe(f.getFill())).a(","); sb.a(describe(f.getInsets())).a(", "); sb.a(describe(f.getRadii())).a(">"); return sb.toString(); } protected String describeDouble(Double x) { if(x == null) { return "null"; } double v = x.doubleValue(); if(v == Double.MAX_VALUE) { return "MAX_VALUE"; } else if(v == Double.MIN_VALUE) { return "MAX_VALUE"; } else if(Double.isNaN(v)) { return "NaN"; } else { long n = x.longValue(); if(n == v) { return String.valueOf(n); } else { return String.valueOf(v); } } } protected String describeFont(Font f) { SB sb = new SB(); sb.a(f.getName()).sp().a(f.getStyle()).sp().a(f.getSize()); return sb.toString(); } protected Object describe(Object x) { if(x instanceof Double) { return describeDouble((Double)x); } else if(x instanceof Double[]) { Double[] v = (Double[])x; SB sb = new SB(); sb.a("<"); boolean sep = false; for(int i=0; i<v.length; i++) { if(sep) { sb.a(", "); } else { sep = true; } sb.a(describeDouble(v[i])); } sb.a(">"); return sb.toString(); } else if(x instanceof Background) { return describeBackground((Background)x); } else if(x instanceof BackgroundFill) { return describeBackgroundFill((BackgroundFill)x); } else if(x instanceof Font) { return describeFont((Font)x); } return x; } protected void dump(Node n) { SB sb = new SB(4096); sb.nl(); while(n != null) { sb.a(CKit.getSimpleName(n)); String id = n.getId(); if(CKit.isNotBlank(id)) { sb.a(" #"); sb.a(id); } for(String s: n.getStyleClass()) { sb.a(" .").a(s); } for(PseudoClass c: n.getPseudoClassStates()) { sb.a(" :").a(c); } sb.nl(); if(n instanceof Text) { sb.sp(4); sb.a("text: "); sb.a(TextTools.escapeControlsForPrintout(((Text)n).getText())); sb.nl(); } CList<CssMetaData<? extends Styleable,?>> md = new CList<>(n.getCssMetaData()); sort(md); for(CssMetaData d: md) { String k = d.getProperty(); Object v = d.getStyleableProperty(n).getValue(); if(shouldShow(v)) { Object val = describe(v); sb.sp(4).a(k); sb.sp().a(val); if(d.isInherits()) { sb.a(" *"); } sb.nl(); } } n = n.getParent(); } D.print(sb); } }