diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 353737ab63..edcd93213b 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -94,6 +94,7 @@ jsvg = { group = "com.github.weisj", name = "jsvg", version = "1.4.0" }
handlebars = { group = "com.github.jknack", name = "handlebars", version.ref = "handlebars" }
handlebars-helpers = { group = "com.github.jknack", name = "handlebars-helpers", version.ref = "handlebars" }
+handlebars-json = { group = "com.github.jknack", name = "handlebars-jackson2", version = "4.3.1" }
# Apache commons and other utilities
# parsing of configuration data
@@ -213,7 +214,7 @@ flatlaf = [
"flatlaf-extras",
"flatlaf-jide-oss",
]
-handlebars = ["handlebars", "handlebars-helpers"]
+handlebars = ["handlebars", "handlebars-helpers", "handlebars-json"]
junit = ["junit-api", "junit-engine", "junit-params"]
jai-imageio = ["jai-imageio-core", "jai-imageio-jpeg"]
graalvm-js = ["graalvm-js", "graalvm-js-scriptengine"]
diff --git a/src/main/java/net/rptools/maptool/client/events/TokenHoverEnter.java b/src/main/java/net/rptools/maptool/client/events/TokenHoverEnter.java
index 34c73d74db..644710d53b 100644
--- a/src/main/java/net/rptools/maptool/client/events/TokenHoverEnter.java
+++ b/src/main/java/net/rptools/maptool/client/events/TokenHoverEnter.java
@@ -24,5 +24,7 @@
* @param zone the zone that the token is in.
* @param shiftDown is the shift key down.
* @param controlDown is the control key down.
+ * @param altDown is the alt-key down.
*/
-public record TokenHoverEnter(Token token, Zone zone, boolean shiftDown, boolean controlDown) {}
+public record TokenHoverEnter(
+ Token token, Zone zone, boolean shiftDown, boolean controlDown, boolean altDown) {}
diff --git a/src/main/java/net/rptools/maptool/client/events/TokenHoverExit.java b/src/main/java/net/rptools/maptool/client/events/TokenHoverExit.java
index 12a18fd132..a17cc670e9 100644
--- a/src/main/java/net/rptools/maptool/client/events/TokenHoverExit.java
+++ b/src/main/java/net/rptools/maptool/client/events/TokenHoverExit.java
@@ -24,5 +24,7 @@
* @param zone the zone for the event.
* @param shiftDown is the shift key down.
* @param controlDown is the control key down.
+ * @param altDown is the alt-key down.
*/
-public record TokenHoverExit(Token token, Zone zone, boolean shiftDown, boolean controlDown) {}
+public record TokenHoverExit(
+ Token token, Zone zone, boolean shiftDown, boolean controlDown, boolean altDown) {}
diff --git a/src/main/java/net/rptools/maptool/client/functions/ShapeFunctions.java b/src/main/java/net/rptools/maptool/client/functions/ShapeFunctions.java
index 0edb8b6288..65457004e2 100644
--- a/src/main/java/net/rptools/maptool/client/functions/ShapeFunctions.java
+++ b/src/main/java/net/rptools/maptool/client/functions/ShapeFunctions.java
@@ -614,6 +614,7 @@ private Object getProperties(
seg, coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], coords[6]));
pi.next();
}
+
StringBuilder stringBuilder = new StringBuilder(sd.toNonLocalisedString());
stringBuilder.append("segments=").append(String.join(",", segments)).append(";");
diff --git a/src/main/java/net/rptools/maptool/client/swing/SwingUtil.java b/src/main/java/net/rptools/maptool/client/swing/SwingUtil.java
index 3f51a03de2..24fc96c23f 100644
--- a/src/main/java/net/rptools/maptool/client/swing/SwingUtil.java
+++ b/src/main/java/net/rptools/maptool/client/swing/SwingUtil.java
@@ -108,6 +108,22 @@ public static boolean isShiftDown(int modifiers) {
return (modifiers & InputEvent.SHIFT_DOWN_MASK) == InputEvent.SHIFT_DOWN_MASK;
}
+ public static boolean isAltDown(InputEvent e) {
+ return isAltDown(e.getModifiersEx());
+ }
+
+ /**
+ * Passed the event's extended modifiers this method returns true if the Alt key,
+ * Right Alt key or Alt-Graph key is down.
+ *
+ * @param modifiers as returned by {@link InputEvent#getModifiersEx()}
+ * @return true if Alt/Right-Alt/Alt-Graph key is down
+ */
+ public static boolean isAltDown(int modifiers) {
+ return (modifiers & InputEvent.ALT_DOWN_MASK) == InputEvent.ALT_DOWN_MASK
+ || (modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) == InputEvent.ALT_GRAPH_DOWN_MASK;
+ }
+
/**
* Centers the innerWindow over the outerWindow. Basically, this method finds the centerpoint of
* the outerWindow and sets the location of innerWindow so that it's
diff --git a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java
index 774a4e1f65..3d95863e4a 100644
--- a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java
+++ b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java
@@ -645,7 +645,8 @@ public void mouseMoved(MouseEvent e) {
oldTokenUnderMouse,
getZone(),
SwingUtil.isShiftDown(keysDown),
- SwingUtil.isControlDown(keysDown)));
+ SwingUtil.isControlDown(keysDown),
+ SwingUtil.isAltDown(keysDown)));
}
} else if (tokenUnderMouse != oldTokenUnderMouse) {
statSheet = null;
@@ -657,7 +658,8 @@ public void mouseMoved(MouseEvent e) {
oldTokenUnderMouse,
getZone(),
SwingUtil.isShiftDown(keysDown),
- SwingUtil.isControlDown(keysDown)));
+ SwingUtil.isControlDown(keysDown),
+ SwingUtil.isAltDown(keysDown)));
}
new MapToolEventBus()
.getMainEventBus()
@@ -666,7 +668,8 @@ public void mouseMoved(MouseEvent e) {
tokenUnderMouse,
getZone(),
SwingUtil.isShiftDown(keysDown),
- SwingUtil.isControlDown(keysDown)));
+ SwingUtil.isControlDown(keysDown),
+ SwingUtil.isAltDown(keysDown)));
}
Token marker = renderer.getMarkerAt(mouseX, mouseY);
if (!AppUtil.tokenIsVisible(renderer.getZone(), marker, renderer.getPlayerView())) {
diff --git a/src/main/java/net/rptools/maptool/client/ui/sheet/stats/StatSheet.java b/src/main/java/net/rptools/maptool/client/ui/sheet/stats/StatSheet.java
index 2ed423b840..296fce4286 100644
--- a/src/main/java/net/rptools/maptool/client/ui/sheet/stats/StatSheet.java
+++ b/src/main/java/net/rptools/maptool/client/ui/sheet/stats/StatSheet.java
@@ -14,36 +14,38 @@
*/
package net.rptools.maptool.client.ui.sheet.stats;
+import com.github.jknack.handlebars.*;
import java.io.IOException;
import java.net.URL;
+import java.util.*;
import javafx.application.Platform;
import net.rptools.maptool.client.AppConstants;
import net.rptools.maptool.client.MapTool;
+import net.rptools.maptool.client.events.TokenHoverEnter;
import net.rptools.maptool.client.ui.htmlframe.HTMLContent;
-import net.rptools.maptool.model.Token;
import net.rptools.maptool.model.sheet.stats.StatSheetContext;
import net.rptools.maptool.model.sheet.stats.StatSheetLocation;
import net.rptools.maptool.util.HandlebarsUtil;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/** Class that represents a pop up stat sheet. */
public class StatSheet {
-
/** Object for logging messages. */
- private static final Logger log = LogManager.getLogger(StatSheet.class);
+ private static final Logger log = LoggerFactory.getLogger(StatSheet.class);
/**
* Sets the content for the stat sheet. The content is a HTML page that is rendered using the
* Handlebars template engine.
*
- * @param token the token to render the stat sheet for.
+ * @param event the token hover event triggering the stat-sheet rendering.
* @param content the content of the stat sheet.
* @param location the location of the stat sheet.
*/
- public void setContent(Token token, String content, URL entry, StatSheetLocation location) {
+ public void setContent(
+ TokenHoverEnter event, String content, URL entry, StatSheetLocation location) {
try {
- var statSheetContext = new StatSheetContext(token, MapTool.getPlayer(), location);
+ var statSheetContext = new StatSheetContext(event, MapTool.getPlayer(), location);
var output =
HTMLContent.htmlFromString(new HandlebarsUtil<>(content, entry).apply(statSheetContext))
.injectURLBase(entry);
diff --git a/src/main/java/net/rptools/maptool/client/ui/sheet/stats/StatSheetListener.java b/src/main/java/net/rptools/maptool/client/ui/sheet/stats/StatSheetListener.java
index 9e7c001e03..c39201e107 100644
--- a/src/main/java/net/rptools/maptool/client/ui/sheet/stats/StatSheetListener.java
+++ b/src/main/java/net/rptools/maptool/client/ui/sheet/stats/StatSheetListener.java
@@ -58,9 +58,9 @@ public void onHoverEnter(TokenHoverEnter event) {
var token = event.token();
if (MapTool.getPlayer().isGM()
|| AppUtil.playerOwns(token)
- || token.getType() != Type.NPC) {
+ || token.getType() == Type.NPC) {
statSheet.setContent(
- event.token(),
+ event,
ssManager.getStatSheetContent(ssId),
ssRecord.entry(),
ssProperties.location());
diff --git a/src/main/java/net/rptools/maptool/model/sheet/stats/StatSheetContext.java b/src/main/java/net/rptools/maptool/model/sheet/stats/StatSheetContext.java
index 1895823f0b..e7d023822f 100644
--- a/src/main/java/net/rptools/maptool/model/sheet/stats/StatSheetContext.java
+++ b/src/main/java/net/rptools/maptool/model/sheet/stats/StatSheetContext.java
@@ -14,26 +14,35 @@
*/
package net.rptools.maptool.model.sheet.stats;
-import java.awt.Dimension;
-import java.util.ArrayList;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.text.Collator;
+import java.util.*;
import java.util.List;
-import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Stream;
import net.rptools.lib.AwtUtil;
import net.rptools.lib.MD5Key;
import net.rptools.maptool.client.AppPreferences;
import net.rptools.maptool.client.AppUtil;
import net.rptools.maptool.client.MapTool;
import net.rptools.maptool.client.MapToolVariableResolver;
-import net.rptools.maptool.model.Token;
+import net.rptools.maptool.client.events.TokenHoverEnter;
+import net.rptools.maptool.client.ui.token.AbstractTokenOverlay;
+import net.rptools.maptool.client.ui.token.BarTokenOverlay;
import net.rptools.maptool.model.player.Player;
import net.rptools.maptool.util.HTMLUtil;
import net.rptools.maptool.util.ImageManager;
+import org.apache.commons.beanutils.BeanUtilsBean;
+import org.apache.commons.beanutils.PropertyUtilsBean;
/** Class that extracts and provides the information needed to render a stat sheet. */
+@SuppressWarnings("unused")
public class StatSheetContext {
-
/** Class that represents a token property on a stat sheet. */
- static class Property {
+ public static class Property {
/** Name of the property. */
private final String name;
@@ -56,7 +65,7 @@ static class Property {
* @param displayName Display Name of the property.
* @param value Value of the property.
* @param gmOnly True if the property is GM only.
- * @note GM only properties are only extracted if the player is a GM.
+ * @implNote GM only properties are only extracted if the player is a GM.
*/
Property(String name, String displayName, String shortName, Object value, boolean gmOnly) {
this.name = name;
@@ -127,6 +136,9 @@ public String getShortName() {
/** The portrait asset of the token. */
private final MD5Key portraitAsset;
+ /** The handout asset of the token. */
+ private final MD5Key handoutAsset;
+
/** The width of the portrait on the stat sheet. */
private final int portraitWidth;
@@ -139,6 +151,12 @@ public String getShortName() {
/** The properties of the token. */
private final List properties = new ArrayList<>();
+ /** The bars shown on the token. */
+ private final List