From 0db87d29659a1ed27e353d05b16dfe4e2bcea022 Mon Sep 17 00:00:00 2001 From: Brian Reynolds Date: Mon, 19 Feb 2024 16:07:32 -0500 Subject: [PATCH 1/4] Multi-Location Command gets sort order controls --- .../map/boardPicker/board/mapgrid/Zone.java | 9 +++ .../VASSAL/counters/MultiLocationCommand.java | 73 +++++++++++++++++-- .../java/VASSAL/counters/SendToLocation.java | 4 +- .../resources/VASSAL/i18n/Editor.properties | 4 + 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/vassal-app/src/main/java/VASSAL/build/module/map/boardPicker/board/mapgrid/Zone.java b/vassal-app/src/main/java/VASSAL/build/module/map/boardPicker/board/mapgrid/Zone.java index 36c9fd516e..a53c433a5b 100644 --- a/vassal-app/src/main/java/VASSAL/build/module/map/boardPicker/board/mapgrid/Zone.java +++ b/vassal-app/src/main/java/VASSAL/build/module/map/boardPicker/board/mapgrid/Zone.java @@ -444,6 +444,15 @@ public Point snapTo(Point p) { return snapTo(p, false, false); } + /** + * @return a center point for the zone + */ + public Point getCenter() { + final Rectangle r = getBounds(); + final Rectangle r2 = getBoard().bounds(); + return new Point(r2.x + r.x + r.width / 2, r2.y + r.y + r.height / 2); + } + @Override public Dimension getSize() { return myPolygon.getBounds().getSize(); diff --git a/vassal-app/src/main/java/VASSAL/counters/MultiLocationCommand.java b/vassal-app/src/main/java/VASSAL/counters/MultiLocationCommand.java index 5d70ecf7e9..7f2d670712 100644 --- a/vassal-app/src/main/java/VASSAL/counters/MultiLocationCommand.java +++ b/vassal-app/src/main/java/VASSAL/counters/MultiLocationCommand.java @@ -43,10 +43,12 @@ import VASSAL.tools.FormattedString; import VASSAL.tools.NamedKeyStroke; import VASSAL.tools.SequenceEncoder; +import org.apache.commons.lang3.math.NumberUtils; import javax.swing.KeyStroke; import java.awt.Component; import java.awt.Graphics; +import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.event.ActionEvent; @@ -68,6 +70,8 @@ public class MultiLocationCommand extends Decorator implements TranslatablePiece public static final String LOC_ZONE = "ZoneOfCommand"; //NON-NLS public static final String LOC_BOARD = "BoardOfCommand"; //NON-NLS public static final String LOC_MAP = "MapOfCommand"; //NON-NLS + public static final String LOC_X = "XOfCommand"; //NON-NLS + public static final String LOC_Y = "YOfCommand"; //NON-NLS public static final String LOC_REGIONS = "locRegions"; //NON-NLS public static final String LOC_ZONES = "locZones"; //NON-NLS @@ -80,6 +84,9 @@ public class MultiLocationCommand extends Decorator implements TranslatablePiece protected Boolean curMapOnly = true; protected PropertyExpression propertiesFilter = new PropertyExpression(""); protected FormattedString menuText = new FormattedString(""); + protected FormattedString menuSort = new FormattedString(""); + protected Boolean ascending = true; + protected Boolean numeric = true; protected NamedKeyStroke key; // Private stuff (shhhh!) @@ -89,6 +96,8 @@ public class MultiLocationCommand extends Decorator implements TranslatablePiece private String evalZone = ""; private String evalBoard = ""; private String evalMap = ""; + private String evalX =""; + private String evalY =""; private boolean everBuilt = false; /** @@ -101,13 +110,19 @@ public static class MultiLocationKeyCommand extends KeyCommand { private final String zoneName; private final String boardName; private final String mapName; + private final String xName; + private final String yName; + private final String sortKey; - public MultiLocationKeyCommand(String name, NamedKeyStroke key, GamePiece target, TranslatablePiece i18nPiece, String locationName, String zoneName, String boardName, String mapName) { + public MultiLocationKeyCommand(String name, NamedKeyStroke key, GamePiece target, TranslatablePiece i18nPiece, String locationName, String zoneName, String boardName, String mapName, String xName, String yName, String sortKey) { super(name, key, target, i18nPiece); this.locationName = locationName; this.zoneName = zoneName; this.boardName = boardName; this.mapName = mapName; + this.xName = xName; + this.yName = yName; + this.sortKey = sortKey; } public String getLocationName() { @@ -141,6 +156,12 @@ else if (LOC_BOARD.equals(key)) { else if (LOC_MAP.equals(key)) { return evalMap; } + else if (LOC_X.equals(key)) { + return evalX; + } + else if (LOC_Y.equals(key)) { + return evalY; + } else { return getOutermost(piece).getProperty(key); } @@ -173,6 +194,9 @@ public void mySetType(String type) { menuText.setFormat(st.nextToken(Resources.getString("Editor.MultiLocationCommand.loc_default_command"))); key = st.nextNamedKeyStroke(); curMapOnly = st.nextBoolean(true); + menuSort.setFormat(st.nextToken("")); + ascending = st.nextBoolean(true); + numeric = st.nextBoolean(false); } @Override @@ -183,7 +207,10 @@ public String myGetType() { .append(propertiesFilter.getExpression()) .append(menuText.getFormat()) .append(key) - .append(curMapOnly); + .append(curMapOnly) + .append(menuSort.getFormat()) + .append(ascending) + .append(numeric); return ID + se.getValue(); } @@ -242,7 +269,11 @@ private void tryName(String name) { if (propertiesFilter.getExpression().isBlank() || propertiesFilter.isTrue(locPS)) { final AuditTrail audit = AuditTrail.create(locPS, menuText.getFormat(), Resources.getString("Editor.MultiLocationCommand.evaluate_menu_text")); final String myMenuText = menuText.getLocalizedText(locPS, this, audit); - keyCommands.add(new MultiLocationKeyCommand(myMenuText, key, getOutermost(this), this, evalLocation, evalZone, evalBoard, evalMap)); + + final AuditTrail audit2 = AuditTrail.create(locPS, menuSort.getFormat(), Resources.getString("Editor.MultiLocationCommand.evaluate_sort_string")); + final String myMenuSort = menuSort.getLocalizedText(locPS, this, audit2); + + keyCommands.add(new MultiLocationKeyCommand(myMenuText, key, getOutermost(this), this, evalLocation, evalZone, evalBoard, evalMap, evalX, evalY, myMenuSort)); } } @@ -251,6 +282,9 @@ private void tryName(String name) { * @param zone zone to test */ private void tryZone(Zone zone) { + final Point center = zone.getCenter(); + evalX = String.valueOf(center.x); + evalY = String.valueOf(center.y); tryName(zone.getName()); } @@ -259,6 +293,11 @@ private void tryZone(Zone zone) { * @param region region to test */ private void tryRegion(Region region) { + final Rectangle r2 = region.getBoard().bounds(); + final Point loc = new Point(region.getOrigin().x + r2.x, region.getOrigin().y + r2.y); + + evalX = String.valueOf(loc.x); + evalY = String.valueOf(loc.y); tryName(region.getName()); } @@ -325,6 +364,10 @@ public KeyCommand[] myGetKeyCommands() { } } + keyCommands.sort((left, right) -> ((numeric && NumberUtils.isParsable(left.sortKey) && NumberUtils.isParsable(right.sortKey)) ? + Integer.compare(NumberUtils.toInt(left.sortKey), NumberUtils.toInt(right.sortKey)) * (ascending ? 1 : -1) : + left.sortKey.compareTo(right.sortKey) * (ascending ? 1 : -1))); + return keyCommands.toArray(new KeyCommand[0]); } @@ -361,6 +404,8 @@ public Command myKeyEvent(KeyStroke stroke) { setProperty(LOC_ZONE, ((MultiLocationKeyCommand) kc).zoneName); setProperty(LOC_BOARD, ((MultiLocationKeyCommand) kc).boardName); setProperty(LOC_MAP, ((MultiLocationKeyCommand) kc).mapName); + setProperty(LOC_X, ((MultiLocationKeyCommand) kc).xName); + setProperty(LOC_Y, ((MultiLocationKeyCommand) kc).yName); } return null; @@ -412,6 +457,9 @@ public boolean testEquals(Object o) { if (!Objects.equals(propertiesFilter.getExpression(), c.propertiesFilter.getExpression())) return false; if (!Objects.equals(menuText.getFormat(), c.menuText.getFormat())) return false; if (!Objects.equals(curMapOnly, c.curMapOnly)) return false; + if (!Objects.equals(menuSort.getFormat(), c.menuSort.getFormat())) return false; + if (!Objects.equals(ascending, c.ascending)) return false; + if (!Objects.equals(numeric, c.numeric)) return false; return Objects.equals(key, c.key); } @@ -425,7 +473,7 @@ public HelpFile getHelpFile() { */ @Override public List getPropertyNames() { - return Arrays.asList(LOC_BOARD, LOC_MAP, LOC_NAME, LOC_ZONE); + return Arrays.asList(LOC_BOARD, LOC_MAP, LOC_NAME, LOC_ZONE, LOC_X, LOC_Y); } public static class Ed implements PieceEditor { @@ -435,6 +483,9 @@ public static class Ed implements PieceEditor { private final PropertyExpressionConfigurer propertyMatchInput; private final FormattedStringConfigurer menuTextInput; private final NamedHotKeyConfigurer keyInput; + private final FormattedStringConfigurer menuSortInput; + private final BooleanConfigurer ascendingInput; + private final BooleanConfigurer numericInput; private final TraitConfigPanel controls; @@ -464,6 +515,15 @@ public Ed(MultiLocationCommand p) { menuTextInput = new FormattedExpressionConfigurer(p.menuText.getFormat(), p); controls.add("Editor.MultiLocationCommand.menu_format", menuTextInput); + menuSortInput = new FormattedExpressionConfigurer(p.menuSort.getFormat(), p); + controls.add("Editor.MultiLocationCommand.menu_sort", menuSortInput); + + ascendingInput = new BooleanConfigurer(p.ascending); + controls.add("Editor.MultiLocationCommand.ascending", ascendingInput); + + numericInput = new BooleanConfigurer(p.numeric); + controls.add("Editor.MultiLocationCommand.numeric", numericInput); + keyInput = new NamedHotKeyConfigurer(p.key); controls.add("Editor.MultiLocationCommand.key_command", keyInput); } @@ -481,7 +541,10 @@ public String getType() { .append(propertyMatchInput.getValueString()) .append(menuTextInput.getValueString()) .append(keyInput.getValueString()) - .append(curMapOnlyInput.getValueString()); + .append(curMapOnlyInput.getValueString()) + .append(menuSortInput.getValueString()) + .append(ascendingInput.getValueString()) + .append(numericInput.getValueString()); return ID + se.getValue(); } diff --git a/vassal-app/src/main/java/VASSAL/counters/SendToLocation.java b/vassal-app/src/main/java/VASSAL/counters/SendToLocation.java index 73a706e4aa..bd119a04eb 100644 --- a/vassal-app/src/main/java/VASSAL/counters/SendToLocation.java +++ b/vassal-app/src/main/java/VASSAL/counters/SendToLocation.java @@ -436,9 +436,7 @@ else if (auditSource instanceof AbstractConfigurable) { } } else { - final Rectangle r = z.getBounds(); - final Rectangle r2 = z.getBoard().bounds(); - dest = new Point(r2.x + r.x + r.width / 2, r2.y + r.y + r.height / 2); + dest = z.getCenter(); } break; diff --git a/vassal-app/src/main/resources/VASSAL/i18n/Editor.properties b/vassal-app/src/main/resources/VASSAL/i18n/Editor.properties index cb1b17dd2d..6b5ed521b0 100644 --- a/vassal-app/src/main/resources/VASSAL/i18n/Editor.properties +++ b/vassal-app/src/main/resources/VASSAL/i18n/Editor.properties @@ -1720,7 +1720,11 @@ Editor.MultiLocationCommand.evaluate_menu_text=Evaluating menu text in Location Editor.MultiLocationCommand.location_type=Type of Location Editor.MultiLocationCommand.matching_properties=Matching Properties Editor.MultiLocationCommand.menu_format=Menu item format +Editor.MultiLocationCommand.menu_sort=Sort menu items by this key string +Editor.MultiLocationCommand.ascending=Ascending sort +Editor.MultiLocationCommand.numeric=Numeric sort Editor.MultiLocationCommand.key_command=Key Command generated by menu item +Editor.MultiLocationCommand.evaluate_sort_string=Evaluating sort string in Location Command # Multi-Zone Grid Editor.MultiZoneGrid.component_type=Multi-zoned Grid From 89a8814dfd0fa2ac64bc61e7d1bceee2e90f852d Mon Sep 17 00:00:00 2001 From: Brian Reynolds Date: Mon, 19 Feb 2024 16:17:56 -0500 Subject: [PATCH 2/4] doxors --- .../ReferenceManual/MultiLocationCommand.adoc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vassal-doc/src/main/readme-referencemanual/ReferenceManual/MultiLocationCommand.adoc b/vassal-doc/src/main/readme-referencemanual/ReferenceManual/MultiLocationCommand.adoc index a3266de134..3b6821e3c8 100644 --- a/vassal-doc/src/main/readme-referencemanual/ReferenceManual/MultiLocationCommand.adoc +++ b/vassal-doc/src/main/readme-referencemanual/ReferenceManual/MultiLocationCommand.adoc @@ -28,6 +28,12 @@ Whenever the player selects _any_ of the menu items associated with a Multi-Loca *Menu item format:*:: The format for constructing menu items from the locations. The trait's properties (e.g., _LocationOfCommand_, etc) can be used with $..$ to make string substitutions. +*Sort menu items by this keystring:*:: This field lets you provide a keystring to control the order that the generated commands will appear in the context menu. For example ``$LocationOfCommand$`` would sort based on the location name, or ``$YOfCommand`` would order the list based on the Y position (i.e. from north to south). + +*Ascending sort:*:: Controls whether an ascending or descending sort is used. + +*Numeric sort:*:: If this box is checked, the sort key is considered to be a numeric field (so that e.g. "90" is treated as lower than "100" which wouldn't be true in an alphabetic sort). If unchecked an alphabetic sort is performed. + *Key command generated by menu item:*:: Whenever any menu item generated by this trait is picked by the player, this key command will be generated. |image:images/MultiLocationCommand.png[] @@ -55,5 +61,9 @@ A Multi-Location trait exposes the following <>. * _MapOfCommand_ The map of the command +* _XOfCommand_ The X position on the map of the command + +* _YOfCommand_ The Y position on the map of the command + From efc9a92e2aec853bbb191c94958eeb70d28ad973 Mon Sep 17 00:00:00 2001 From: Brian Reynolds Date: Mon, 19 Feb 2024 16:22:38 -0500 Subject: [PATCH 3/4] whitespace failures --- .../src/main/java/VASSAL/counters/MultiLocationCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vassal-app/src/main/java/VASSAL/counters/MultiLocationCommand.java b/vassal-app/src/main/java/VASSAL/counters/MultiLocationCommand.java index 7f2d670712..2eceb09761 100644 --- a/vassal-app/src/main/java/VASSAL/counters/MultiLocationCommand.java +++ b/vassal-app/src/main/java/VASSAL/counters/MultiLocationCommand.java @@ -96,8 +96,8 @@ public class MultiLocationCommand extends Decorator implements TranslatablePiece private String evalZone = ""; private String evalBoard = ""; private String evalMap = ""; - private String evalX =""; - private String evalY =""; + private String evalX = ""; + private String evalY = ""; private boolean everBuilt = false; /** From 48d6b9b22474159aaf0ecfd3535842ccb2095a98 Mon Sep 17 00:00:00 2001 From: Brian Reynolds Date: Mon, 19 Feb 2024 16:33:55 -0500 Subject: [PATCH 4/4] sigh --- .../java/VASSAL/counters/MultiLocationCommand.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vassal-app/src/main/java/VASSAL/counters/MultiLocationCommand.java b/vassal-app/src/main/java/VASSAL/counters/MultiLocationCommand.java index 2eceb09761..e5822318cd 100644 --- a/vassal-app/src/main/java/VASSAL/counters/MultiLocationCommand.java +++ b/vassal-app/src/main/java/VASSAL/counters/MultiLocationCommand.java @@ -114,6 +114,18 @@ public static class MultiLocationKeyCommand extends KeyCommand { private final String yName; private final String sortKey; + // For backwards compatibility + public MultiLocationKeyCommand(String name, NamedKeyStroke key, GamePiece target, TranslatablePiece i18nPiece, String locationName, String zoneName, String boardName, String mapName) { + super(name, key, target, i18nPiece); + this.locationName = locationName; + this.zoneName = zoneName; + this.boardName = boardName; + this.mapName = mapName; + xName = ""; + yName = ""; + sortKey = ""; + } + public MultiLocationKeyCommand(String name, NamedKeyStroke key, GamePiece target, TranslatablePiece i18nPiece, String locationName, String zoneName, String boardName, String mapName, String xName, String yName, String sortKey) { super(name, key, target, i18nPiece); this.locationName = locationName;