From 9ffd8becba9469b3fa89d0f8a30db4ee7c41d660 Mon Sep 17 00:00:00 2001 From: Craig Wisniewski Date: Thu, 10 Aug 2023 21:09:46 +0930 Subject: [PATCH 001/146] external add on dialog --- build.gradle | 3 + .../ui/addon/AddOnLibrariesDialogView.form | 136 ++++++++-- .../ui/addon/AddOnLibrariesDialogView.java | 42 ++++ .../ExternalAddOnLibrariesTableModel.java | 73 ++++++ .../maptool/model/library/LibraryInfo.java | 5 +- .../maptool/model/library/LibraryManager.java | 47 ++++ .../model/library/addon/AddOnLibrary.java | 69 ++++- .../library/addon/AddOnLibraryImporter.java | 12 + .../library/addon/AddOnLibraryManager.java | 79 ++++++ .../addon/ExternalAddOnLibraryManager.java | 238 ++++++++++++++++++ .../builtin/MapToolBuiltInLibrary.java | 3 +- .../model/library/token/LibraryToken.java | 1 + .../rptools/maptool/language/i18n.properties | 12 +- 13 files changed, 689 insertions(+), 31 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java create mode 100644 src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java diff --git a/build.gradle b/build.gradle index f728a71f88..48a4451550 100644 --- a/build.gradle +++ b/build.gradle @@ -496,6 +496,9 @@ dependencies { implementation 'com.github.jknack:handlebars:4.3.1' implementation 'com.github.jknack:handlebars-helpers:4.3.1' + + // File watcher library to work around some inconsistencies with java.nio.file.WatchService + implementation 'io.methvin:directory-watcher:0.18.0' } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form index e27669d642..801bad3aff 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form @@ -275,7 +275,7 @@ - + @@ -283,40 +283,128 @@ - + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index 75137f8120..182d4aaf54 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -27,6 +27,7 @@ import java.util.Optional; import java.util.concurrent.ExecutionException; import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFileChooser; @@ -34,6 +35,7 @@ import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.JTable; +import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.KeyStroke; import net.rptools.maptool.client.AppActions.MapPreviewFileChooser; @@ -69,6 +71,12 @@ public class AddOnLibrariesDialogView extends JDialog { private JButton viewLicenseFileButton; private JButton copyThemeCSS; private JButton copyStatSheetThemeButton; + private JCheckBox enableExternalAddOnCheckBox; + private JTable externalAddonTable; + private JButton createAddonSkeletonButton; + private JTextField directoryTextField; + private JButton browseButton; + private JButton exportAddOn; private LibraryInfo selectedAddOn; @@ -80,6 +88,9 @@ public AddOnLibrariesDialogView() { addOnLibraryTable.setModel(new AddOnLibrariesTableModel()); addOnLibraryTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + externalAddonTable.setModel(new ExternalAddOnLibrariesTableModel()); + externalAddonTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + buttonRemove.setEnabled(false); addOnLibraryTable .getSelectionModel() @@ -205,6 +216,37 @@ public void actionPerformed(ActionEvent e) { .setContents(new StringSelection(themeCss), null); }); }); + + createAddonSkeletonButton.addActionListener( + e -> { + createAddonSkeleton(); + }); + + enableExternalAddOnCheckBox.addActionListener( + e -> { + setExternalAddOnControlsEnabled(enableExternalAddOnCheckBox.isSelected()); + directoryTextField.setEnabled(enableExternalAddOnCheckBox.isSelected()); + new LibraryManager() + .setExternalLibrariesEnabled(enableExternalAddOnCheckBox.isSelected()); + }); + + browseButton.addActionListener( e -> { + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle(I18N.getText("library.dialog.import.title")); + }); + + LibraryManager libraryManager = new LibraryManager(); + enableExternalAddOnCheckBox.setSelected(libraryManager.externalLibrariesEnabled()); + setExternalAddOnControlsEnabled(enableExternalAddOnCheckBox.isSelected()); + } + + private void setExternalAddOnControlsEnabled(boolean selected) { + externalAddonTable.setEnabled(enableExternalAddOnCheckBox.isSelected()); + browseButton.setEnabled(enableExternalAddOnCheckBox.isSelected()); + } + + private void createAddonSkeleton() { + // TODO: CDW } /** diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java new file mode 100644 index 0000000000..c14f6b471c --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java @@ -0,0 +1,73 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.ui.addon; + +import java.util.List; +import javax.swing.JButton; +import javax.swing.table.AbstractTableModel; +import net.rptools.maptool.language.I18N; +import net.rptools.maptool.model.library.LibraryInfo; +import net.rptools.maptool.model.library.LibraryManager; + +public class ExternalAddOnLibrariesTableModel extends AbstractTableModel { + + @Override + public int getRowCount() { + return new LibraryManager().getExternalAddOnLibraries().size(); + } + + @Override + public int getColumnCount() { + return 5; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + List addons = new LibraryManager().getExternalAddOnLibraries(); + + return switch (columnIndex) { + case 0 -> addons.get(rowIndex).name(); + case 1 -> addons.get(rowIndex).version(); + case 2 -> addons.get(rowIndex).namespace(); + case 3 -> addons.get(rowIndex).backingDirectory(); + case 4 -> { + var button = new JButton(); + button.setText("library.dialog.addon.refresh"); + button.addActionListener( + e -> { + refresh(rowIndex); + }); + yield button; + } + default -> null; + }; + } + + @Override + public String getColumnName(int column) { + return switch (column) { + case 0 -> I18N.getText("library.dialog.addon.name"); + case 1 -> I18N.getText("library.dialog.addon.version"); + case 2 -> I18N.getText("library.dialog.addon.namespace"); + case 3 -> I18N.getText("library.dialog.addon.subdir"); + case 4 -> I18N.getText("library.dialog.addon.refresh"); + default -> null; + }; + } + + void refresh(int rowIndex) { + // TODO: CDW + } +} diff --git a/src/main/java/net/rptools/maptool/model/library/LibraryInfo.java b/src/main/java/net/rptools/maptool/model/library/LibraryInfo.java index 68a239a149..50bf969abf 100644 --- a/src/main/java/net/rptools/maptool/model/library/LibraryInfo.java +++ b/src/main/java/net/rptools/maptool/model/library/LibraryInfo.java @@ -14,6 +14,8 @@ */ package net.rptools.maptool.model.library; +import java.nio.file.Path; + /** Record that contains the information about a library. */ public record LibraryInfo( String name, @@ -27,5 +29,6 @@ public record LibraryInfo( String shortDescription, boolean allowsUrlAccess, String readMeFile, - String licenseFile) {} + String licenseFile, + Path backingDirectory) {} ; diff --git a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java index 07400a2f8c..14bc344639 100644 --- a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java @@ -15,6 +15,7 @@ package net.rptools.maptool.model.library; import java.net.URL; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -71,6 +72,52 @@ public class LibraryManager { static { libraryTokenManager.init(); + addOnLibraryManager.init(); + } + + /** + * Returns the list of external add-on libraries. + * + * @return the list of external add-on libraries. + */ + public List getExternalAddOnLibraries() { + return addOnLibraryManager.getExternalAddOnLibraries(); + } + + /** + * Returns if external add-on libraries are enabled. + * + * @return if external add-on libraries are enabled. + */ + public boolean externalLibrariesEnabled() { + return addOnLibraryManager.externalLibrariesEnabled(); + } + + /** + * Sets if external add-on libraries are enabled. + * + * @param enabled if external add-on libraries are enabled. + */ + public void setExternalLibrariesEnabled(boolean enabled) { + addOnLibraryManager.setExternalLibrariesEnabled(enabled); + } + + /** + * Returns the path to the external add-on libraries. + * + * @return the path to the external add-on libraries. + */ + public Path getEternalLibraryPath() { + return addOnLibraryManager.getExternalLibraryPath(); + } + + /** + * Sets the path to the external add-on libraries. + * + * @param path the path to the external add-on libraries. + */ + public void setExternalLibraryPath(Path path) { + addOnLibraryManager.setExternalLibraryPath(path); } /** diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java index 2be092f6e8..64b150d0d9 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java @@ -19,6 +19,7 @@ import java.io.InputStream; import java.net.URI; import java.net.URL; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -151,6 +152,12 @@ private record MTScript(String path, boolean autoExecute, String description, MD /** The Stat Sheets defined by the add-on library. */ private final Set statSheets = new HashSet<>(); + /** + * The directory that the library is in for development mode, or null if the add-on is not in + * development mode. + */ + private final Path backingDirectory; + /** * Class used to represent Drop In Libraries. * @@ -158,6 +165,8 @@ private record MTScript(String path, boolean autoExecute, String description, MD * @param mtsDto The MTScript Properties Data Transfer Object. * @param eventsDto The MTScript Events Data Transfer Object. * @param pathAssetMap mapping of paths in the library to {@link MD5Key}s and {@link Asset.Type}s. + * @param backingDirectory The directory that the library is in for development mode, or null if + * the add-on is not in development mode. */ private AddOnLibrary( MD5Key libraryAssetKey, @@ -165,7 +174,8 @@ private AddOnLibrary( MTScriptPropertiesDto mtsDto, AddOnLibraryEventsDto eventsDto, AddOnStatSheetsDto statSheetsDto, - Map> pathAssetMap) { + Map> pathAssetMap, + Path backingDirectory) { Objects.requireNonNull(dto, I18N.getText("library.error.invalidDefinition")); name = Objects.requireNonNull(dto.getName(), I18N.getText("library.error.emptyName")); version = @@ -247,6 +257,8 @@ private AddOnLibrary( readMeFile = dto.getReadMeFile(); jsContextName = JS_CONTEXT_PREFIX + namespace; + + this.backingDirectory = backingDirectory; } /** @@ -267,7 +279,31 @@ public static AddOnLibrary fromDto( AddOnStatSheetsDto statSheetsDto, Map> pathAssetMap) { - return new AddOnLibrary(libraryAssetKey, dto, mtsDto, eventsDto, statSheetsDto, pathAssetMap); + return fromDto(libraryAssetKey, dto, mtsDto, eventsDto, statSheetsDto, pathAssetMap, null); + } + + /** + * Creates a new Drop In Library from the given {@link AddOnLibraryDto}, {@link + * MTScriptPropertiesDto}, and file path assets map. + * + * @param dto The Drop In Libraries Data Transfer Object. + * @param mtsDto The MTScript Properties Data Transfer Object. + * @param eventsDto The Events Data Transfer Object. + * @param pathAssetMap mapping of paths in the library to {@link MD5Key}s and {@link Asset.Type}s. + * @param backingDirectory The directory that the library is in for development mode, or null if + * the add-on is not in development mode. + * @return the new Add on library. + */ + public static AddOnLibrary fromDto( + MD5Key libraryAssetKey, + AddOnLibraryDto dto, + MTScriptPropertiesDto mtsDto, + AddOnLibraryEventsDto eventsDto, + AddOnStatSheetsDto statSheetsDto, + Map> pathAssetMap, + Path backingDirectory) { + return new AddOnLibrary( + libraryAssetKey, dto, mtsDto, eventsDto, statSheetsDto, pathAssetMap, backingDirectory); } @Override @@ -305,7 +341,8 @@ public CompletableFuture getLibraryInfo() { shortDescription, allowsUriAccess, readMeFile.isEmpty() ? null : readMeFile, - licenseFile.isEmpty() ? null : licenseFile)); + licenseFile.isEmpty() ? null : licenseFile, + backingDirectory)); } /** @@ -645,6 +682,12 @@ private void runJS(String file) { .join(); } + /** + * Returns the DataValue for the specified path in the add-on library. + * + * @param path the path to the file to read. + * @return the DataValue for the specified path in the add-on library. + */ CompletableFuture readFile(String path) { return CompletableFuture.supplyAsync( () -> { @@ -657,4 +700,24 @@ CompletableFuture readFile(String path) { return DataValueFactory.fromAsset(filePath, asset); }); } + + /** + * Returns if the add-on library is in development mode or not. + * + * @return true if the add-on library is in development mode, false + * otherwise. + */ + public boolean isInDevelopmentMode() { + return backingDirectory != null; + } + + /** + * Returns the directory that the library is in for development mode, or null if the add-on is not + * in development mode. + * + * @return the directory for the add-on. + */ + public Path getBackingDirectory() { + return backingDirectory; + } } diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java index 921866a1e9..790006af02 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java @@ -21,6 +21,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.zip.ZipEntry; @@ -131,6 +132,17 @@ public AddOnLibrary importFromAsset(Asset asset) throws IOException { return importFromFile(tempFile); } + /** + * Imports the add-on library from the specified directory + * + * @param dir the directory to import the library from. + * @return the {@link AddOnLibrary} that was imported. + * @throws IOException if an error occurs while reading the library. + */ + public AddOnLibrary importFromDirectory(Path dir) throws IOException { + return null; // TODO: CDW + } + /** * Imports the add-on library from the specified file. * diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java index 5d04309356..cbd9f61181 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java @@ -15,6 +15,7 @@ package net.rptools.maptool.model.library.addon; import java.net.URL; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -27,6 +28,7 @@ import net.rptools.maptool.model.library.AddOnsAddedEvent; import net.rptools.maptool.model.library.AddOnsRemovedEvent; import net.rptools.maptool.model.library.Library; +import net.rptools.maptool.model.library.LibraryInfo; import net.rptools.maptool.model.library.proto.AddOnLibraryDto; import net.rptools.maptool.model.library.proto.AddOnLibraryListDto; import net.rptools.maptool.model.library.proto.AddOnLibraryListDto.AddOnLibraryEntryDto; @@ -40,6 +42,8 @@ public class AddOnLibraryManager { /** The add-on libraries that are registered. */ private final Map namespaceLibraryMap = new ConcurrentHashMap<>(); + private ExternalAddOnLibraryManager externalAddOnLibraryManager; + /** * Is there a add-on library that would handle this path. This just checks the protocol and * namespace, it won't check that the full path actually exists. @@ -85,6 +89,16 @@ public void registerLibrary(AddOnLibrary library) { .post(new AddOnsAddedEvent(Set.of(library.getLibraryInfo().join()))); } + /** + * Checks to see if the specified namespace is registered. + * + * @param namespace the namespace to check. + * @return {@code true} if the namespace is registered. + */ + public boolean isNamespaceRegistered(String namespace) { + return namespaceLibraryMap.containsKey(namespace.toLowerCase()); + } + /** * Deregister the add-on library with the specified namespace. * @@ -193,4 +207,69 @@ public CompletableFuture> getLegacyEventTargets(String eventName) { .filter(l -> l.getLegacyEvents().contains(eventName)) .collect(Collectors.toSet())); } + + /** Initializes the add-on library manager. */ + public void init() { + externalAddOnLibraryManager = new ExternalAddOnLibraryManager(this); + } + + /** + * Replaces the add-on library with a newer version. + * + * @param library the library to replace the existing library with. + */ + public void replaceLibrary(AddOnLibrary library) { + library + .getNamespace() + .thenAccept( + namespace -> { + deregisterLibrary(namespace); + registerLibrary(library); + }); + } + + /** + * Returns the information of external add-on libraries that are registered. + * + * @return the information of external add-on libraries that are registered. + */ + public List getExternalAddOnLibraries() { + return externalAddOnLibraryManager.getLibraries(); + } + + /** + * Returns if external add-on libraries are enabled. + * + * @return if external add-on libraries are enabled. + */ + public boolean externalLibrariesEnabled() { + return externalAddOnLibraryManager.isEnabled(); + } + + /** + * Sets if external add-on libraries are enabled. + * + * @param enabled if external add-on libraries are enabled. + */ + public void setExternalLibrariesEnabled(boolean enabled) { + externalAddOnLibraryManager.setEnabled(enabled); + } + + /** + * Returns the path to the external add-on libraries. + * + * @return the path to the external add-on libraries. + */ + public Path getExternalLibraryPath() { + return externalAddOnLibraryManager.getExternalLibraryPath(); + } + + /** + * Sets the path to the external add-on libraries. + * + * @param path the path to the external add-on libraries. + */ + public void setExternalLibraryPath(Path path) { + externalAddOnLibraryManager.setExternalLibraryPath(path); + } } diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java new file mode 100644 index 0000000000..2a5f5edf29 --- /dev/null +++ b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java @@ -0,0 +1,238 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.model.library.addon; + +import io.methvin.watcher.DirectoryWatcher; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; +import net.rptools.maptool.model.library.LibraryInfo; + +public class ExternalAddOnLibraryManager { + + /** The add-on library manager that is used to register the add-on libraries. */ + private final AddOnLibraryManager addOnLibraryManager; + + /** The add-on libraries that are registered. */ + private final Map namespaceLibraryMap = new ConcurrentHashMap<>(); + + /** Cache of the library info for the namespace. */ + private final Map namespaceLibraryInfoMap = new ConcurrentHashMap<>(); + + /** Cache of external library info. */ + private final List externalLibraryInfo; + + /** Is the external add-on library manager enabled. */ + private boolean isEnabled = false; + + /** The path to the external add-on libraries. */ + private Path externalLibraryPath = null; + + /** Directory watcher for watching the external add-on library directory. */ + private DirectoryWatcher directoryWatcher; + + /** Lock for managing enabled and path states. */ + private final ReentrantLock lock = new ReentrantLock(); + + /** + * Creates a new instance of the external add-on library manager. + * + * @param addOnLibraryManager the add-on library manager used to register the add-on libraries. + */ + public ExternalAddOnLibraryManager(AddOnLibraryManager addOnLibraryManager) { + this.addOnLibraryManager = addOnLibraryManager; + externalLibraryInfo = Collections.synchronizedList(new ArrayList<>()); + } + + public void registerExternalAddOnLibrary(AddOnLibrary addOnLibrary) { + addOnLibrary + .getNamespace() + .thenAccept( + namespace -> { + if (addOnLibraryManager.isNamespaceRegistered(namespace)) { + addOnLibraryManager.deregisterLibrary(namespace); + } + namespaceLibraryMap.put(namespace, addOnLibrary); + addOnLibraryManager.registerLibrary(addOnLibrary); + addOnLibrary + .getLibraryInfo() + .thenAccept( + libraryInfo -> { + namespaceLibraryInfoMap.put(namespace, libraryInfo); + }); + cacheExternalLibraryInfo(); + }); + } + + public void deregisterExternalAddOnLibrary(AddOnLibrary addOnLibrary) { + addOnLibrary + .getNamespace() + .thenAccept( + namespace -> { + namespaceLibraryMap.remove(namespace); + namespaceLibraryMap.remove(namespace); + addOnLibrary.getLibraryInfo().thenAccept(externalLibraryInfo::remove); + addOnLibraryManager.deregisterLibrary(namespace); + cacheExternalLibraryInfo(); + }); + } + + private void cacheExternalLibraryInfo() { + externalLibraryInfo.clear(); + var infoList = + namespaceLibraryInfoMap.values().stream() + .sorted((li1, li12) -> li1.name().compareToIgnoreCase(li12.name())) + .toList(); + externalLibraryInfo.addAll(infoList); + } + + public void refreshExternalAddOnLibrary(String namespace, Path path) { + try { + var lib = new AddOnLibraryImporter().importFromDirectory(path); + addOnLibraryManager.replaceLibrary(lib); + } catch (IOException e) { + throw new RuntimeException(e); // TODO: CDW + } + } + + public List getLibraries() { + return externalLibraryInfo; + } + + public boolean isEnabled() { + try { + lock.lock(); + return isEnabled; + } finally { + lock.unlock(); + } + } + + public void setEnabled(boolean enabled) { + try { + lock.lock(); + if (isEnabled != enabled) { + isEnabled = enabled; + if (enabled) { + startWatching(); + } else { + stopWatching(); + } + } + } finally { + lock.unlock(); + } + } + + public Path getExternalLibraryPath() { + try { + lock.lock(); + return externalLibraryPath; + } finally { + lock.unlock(); + } + } + + public void setExternalLibraryPath(Path path) { + if (path != null && path.equals(externalLibraryPath)) { + return; + } + try { + lock.lock(); + externalLibraryPath = path; + stopWatching(); + if (path == null) { + isEnabled = false; + } else { + startWatching(); + } + } finally { + lock.unlock(); + } + } + + private void stopWatching() { + try { + lock.lock(); + if (directoryWatcher != null) { + try { + directoryWatcher.close(); + directoryWatcher = null; + } catch (IOException e) { + throw new RuntimeException(e); // TODO: CDW + } + } + } finally { + lock.unlock(); + } + } + + private void startWatching() { + try { + lock.lock(); + if (isEnabled && externalLibraryPath != null && Files.exists(externalLibraryPath)) { + if (directoryWatcher != null) { + directoryWatcher.watchAsync(); + } else { + directoryWatcher = createDirectoryWatcher(); + directoryWatcher.watchAsync(); + } + } + } catch (IOException e) { + throw new RuntimeException(e); // TODO: CDW + } finally { + lock.unlock(); + } + } + + private DirectoryWatcher createDirectoryWatcher() throws IOException { + return DirectoryWatcher.builder() + .path(externalLibraryPath) + .listener( + event -> { + switch (event.eventType()) { + case CREATE -> { + if (event.path().relativize(externalLibraryPath).getNameCount() == 1) { + try { + var lib = new AddOnLibraryImporter().importFromDirectory(event.path()); + registerExternalAddOnLibrary(lib); + } catch (IOException e) { + throw new RuntimeException(e); // TODO: CDW + } + } + } + case DELETE -> { + if (event.path().relativize(externalLibraryPath).getNameCount() == 1) { + deregisterExternalAddOnLibrary( + namespaceLibraryMap.get(event.path().getFileName().toString())); + } + } + case MODIFY -> { + if (event.path().relativize(externalLibraryPath).getNameCount() == 1) { + refreshExternalAddOnLibrary( + event.path().getFileName().toString(), event.path()); + } + } + } + }) + .build(); + } +} diff --git a/src/main/java/net/rptools/maptool/model/library/builtin/MapToolBuiltInLibrary.java b/src/main/java/net/rptools/maptool/model/library/builtin/MapToolBuiltInLibrary.java index 0de2893dce..3de968a2bc 100644 --- a/src/main/java/net/rptools/maptool/model/library/builtin/MapToolBuiltInLibrary.java +++ b/src/main/java/net/rptools/maptool/model/library/builtin/MapToolBuiltInLibrary.java @@ -220,7 +220,8 @@ public CompletableFuture getLibraryInfo() { shortDescription, allowsUriAccess, readMeFile.isEmpty() ? null : readMeFile, - licenseFile.isEmpty() ? null : licenseFile)); + licenseFile.isEmpty() ? null : licenseFile, + null)); } @Override diff --git a/src/main/java/net/rptools/maptool/model/library/token/LibraryToken.java b/src/main/java/net/rptools/maptool/model/library/token/LibraryToken.java index 446be2c6d8..c97b46484b 100644 --- a/src/main/java/net/rptools/maptool/model/library/token/LibraryToken.java +++ b/src/main/java/net/rptools/maptool/model/library/token/LibraryToken.java @@ -296,6 +296,7 @@ public CompletableFuture getLibraryInfo() { shortDescription.isEmpty() ? notSet : shortDescription, allowsUriAccess, null, + null, null)); } diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 4bed6535ec..fefb7ade39 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2760,8 +2760,16 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing \ purposes only.
Within your add-on use
lib://net.rptools.maptool/css/mt-stat-sheet.css \ or
lib://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.export = Export Add-On +library.dialog.directory.label = Add On Directory + + # Game Data data.error.cantConvertTo = Can''t convert {0} to {1}. From 58f657a86fe48617d35d6bc4ea6f8c7af7923783 Mon Sep 17 00:00:00 2001 From: Craig Wisniewski Date: Mon, 14 Aug 2023 16:07:59 +0930 Subject: [PATCH 002/146] formatting --- .../client/ui/addon/AddOnLibrariesDialogView.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index 182d4aaf54..a93cd3ea57 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -230,10 +230,11 @@ public void actionPerformed(ActionEvent e) { .setExternalLibrariesEnabled(enableExternalAddOnCheckBox.isSelected()); }); - browseButton.addActionListener( e -> { - JFileChooser chooser = new JFileChooser(); - chooser.setDialogTitle(I18N.getText("library.dialog.import.title")); - }); + browseButton.addActionListener( + e -> { + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle(I18N.getText("library.dialog.import.title")); + }); LibraryManager libraryManager = new LibraryManager(); enableExternalAddOnCheckBox.setSelected(libraryManager.externalLibrariesEnabled()); From 57c9ff717473bd2a546f63a8c95ea3fa5d1504d4 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Fri, 24 May 2024 09:49:41 +0930 Subject: [PATCH 003/146] Spotless Apply --- .../model/library/addon/AddOnLibrary.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java index 271688b4c0..f7f62d404d 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java @@ -284,7 +284,7 @@ private AddOnLibrary( allowsUriAccess, readMeFile.isEmpty() ? null : readMeFile, licenseFile.isEmpty() ? null : licenseFile, - backingDirectory); + backingDirectory); for (var s : slashCommandsDto.getSlashCommandsList()) { slashCommands.put( @@ -319,7 +319,15 @@ public static AddOnLibrary fromDto( AddonSlashCommandsDto slashCommandsDto, Map> pathAssetMap) { - return new AddOnLibrary(libraryAssetKey, dto, mtsDto, eventsDto, statSheetsDto, slashCommandsDto, pathAssetMap, null); + return new AddOnLibrary( + libraryAssetKey, + dto, + mtsDto, + eventsDto, + statSheetsDto, + slashCommandsDto, + pathAssetMap, + null); } /** @@ -329,7 +337,7 @@ public static AddOnLibrary fromDto( * @param dto The Drop In Libraries Data Transfer Object. * @param mtsDto The MTScript Properties Data Transfer Object. * @param eventsDto The Events Data Transfer Object. - * @param slashCommandsDto The Slash Commands Data Transfer Object. + * @param slashCommandsDto The Slash Commands Data Transfer Object. * @param pathAssetMap mapping of paths in the library to {@link MD5Key}s and {@link Asset.Type}s. * @param backingDirectory The directory that the library is in for development mode, or null if * the add-on is not in development mode. @@ -345,7 +353,14 @@ public static AddOnLibrary fromDto( Map> pathAssetMap, Path backingDirectory) { return new AddOnLibrary( - libraryAssetKey, dto, mtsDto, eventsDto, statSheetsDto, slashCommandsDto, pathAssetMap, backingDirectory); + libraryAssetKey, + dto, + mtsDto, + eventsDto, + statSheetsDto, + slashCommandsDto, + pathAssetMap, + backingDirectory); } @Override From df324c31907540c448c8a7f4c3c1033831df19c8 Mon Sep 17 00:00:00 2001 From: Sparky200 Date: Sun, 26 May 2024 02:29:50 -0500 Subject: [PATCH 004/146] Loader created for external addons --- .../ui/addon/AddOnLibrariesDialogView.java | 10 +- .../maptool/model/library/LibraryManager.java | 19 +++ .../library/addon/AddOnLibraryImporter.java | 148 +++++++++++++++++- .../library/addon/AddOnLibraryManager.java | 16 ++ .../addon/ExternalAddOnLibraryManager.java | 30 ++-- .../net/rptools/maptool/util/FileUtil.java | 31 ++++ 6 files changed, 230 insertions(+), 24 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index a93cd3ea57..a5853f196e 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -24,6 +24,7 @@ import java.io.File; import java.io.IOException; import java.net.URI; +import java.nio.file.Path; import java.util.Optional; import java.util.concurrent.ExecutionException; import javax.swing.JButton; @@ -47,6 +48,7 @@ import net.rptools.maptool.model.library.Library; import net.rptools.maptool.model.library.LibraryInfo; import net.rptools.maptool.model.library.LibraryManager; +import net.rptools.maptool.model.library.addon.AddOnLibrary; import net.rptools.maptool.model.library.addon.AddOnLibraryImporter; /** Dialog for managing add-on libraries. */ @@ -247,7 +249,13 @@ private void setExternalAddOnControlsEnabled(boolean selected) { } private void createAddonSkeleton() { - // TODO: CDW + LibraryManager library = new LibraryManager(); + try { + library.registerExternalAddOnLibrary(new AddOnLibraryImporter() + .importFromDirectory(Path.of(directoryTextField.getText()))); + } catch (IOException e) { + MapTool.showError("library.import.ioError", e); + } } /** diff --git a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java index bcadc84180..2c1339283f 100644 --- a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java @@ -225,6 +225,7 @@ public boolean registerAddOnLibrary(AddOnLibrary addOn) { /** * Deregister the add-on in library associated with the specified namespace. + * This will deregister the add-on if it is external. * * @param namespace the namespace to deregister. */ @@ -255,6 +256,24 @@ public boolean reregisterAddOnLibrary(AddOnLibrary addOnLibrary) { return true; } + /** + * Register an add-on as an external add-on. + * @param addOnLibrary The add-on to register. + * @return Whether the add-on was successfully registered. + * If the result is `false`, the reason will be logged as an error. + */ + public boolean registerExternalAddOnLibrary(AddOnLibrary addOnLibrary) { + try { + addOnLibraryManager.registerExternalLibrary(addOnLibrary); + if (MapTool.isHostingServer()) + MapTool.serverCommand().addAddOnLibrary(List.of(new TransferableAddOnLibrary(addOnLibrary))); + } catch (InterruptedException | ExecutionException e) { + log.error("Error registering external add-on", e); + return false; + } + return true; + } + /** * Returns a list of information about the registered libraries. * diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java index 731f960d3b..b2b03c4fa4 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java @@ -22,10 +22,14 @@ import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; import javax.swing.filechooser.FileFilter; import net.rptools.lib.MD5Key; import net.rptools.maptool.language.I18N; @@ -37,6 +41,7 @@ import net.rptools.maptool.model.library.proto.AddOnStatSheetsDto; import net.rptools.maptool.model.library.proto.AddonSlashCommandsDto; import net.rptools.maptool.model.library.proto.MTScriptPropertiesDto; +import net.rptools.maptool.util.FileUtil; import org.apache.tika.mime.MediaType; import org.javatuples.Pair; @@ -144,7 +149,85 @@ public AddOnLibrary importFromAsset(Asset asset) throws IOException { * @throws IOException if an error occurs while reading the library. */ public AddOnLibrary importFromDirectory(Path dir) throws IOException { - return null; // TODO: CDW + + var infoPath = dir.resolve(LIBRARY_INFO_FILE); + if (!Files.exists(infoPath)) + throw new IOException(I18N.getText("library.error.addOn.noConfigFile", dir)); + + var builder = AddOnLibraryDto.newBuilder(); + JsonFormat.parser() + .ignoringUnknownFields() + .merge(Files.newBufferedReader(infoPath), builder); + + var pathAssetMap = processAssetsFromDirectory(builder.getNamespace(), dir); + + var mtsPropBuilder = MTScriptPropertiesDto.newBuilder(); + var mtsPropPath = dir.resolve(MACROSCRIPT_PROPERTY_FILE); + if (Files.exists(mtsPropPath)) + JsonFormat.parser() + .ignoringUnknownFields() + .merge(Files.newBufferedReader(mtsPropPath), mtsPropBuilder); + + var eventPropBuilder = AddOnLibraryEventsDto.newBuilder(); + var eventPropPath = dir.resolve(EVENT_PROPERTY_FILE); + if (Files.exists(eventPropPath)) + JsonFormat.parser() + .ignoringUnknownFields() + .merge(Files.newBufferedReader(eventPropPath), eventPropBuilder); + + var statSheetsBuilder = AddOnStatSheetsDto.newBuilder(); + var statSheetsPath = dir.resolve(STATS_SHEET_FILE); + if (Files.exists(statSheetsPath)) + JsonFormat.parser() + .ignoringUnknownFields() + .merge(Files.newBufferedReader(statSheetsPath), statSheetsBuilder); + + var slashCommandsBuilder = AddonSlashCommandsDto.newBuilder(); + var slashCommandsPath = dir.resolve(SLASH_COMMAND_FILE); + if (Files.exists(slashCommandsPath)) + JsonFormat.parser() + .ignoringUnknownFields() + .merge(Files.newBufferedReader(slashCommandsPath), slashCommandsBuilder); + + addMetaDataFromDirectory(builder.getNamespace(), dir, pathAssetMap); + + // Directory assets must be zipped up. When external add-on libraries are sent to remotes, + // they should act as normal add-on libraries. + var addOnLib = builder.build(); + + var zipPath = Files.createTempFile(builder.getNamespace(), null); + try (var zipOut = new ZipOutputStream(Files.newOutputStream(zipPath, StandardOpenOption.WRITE))) { + var paths = pathAssetMap.keySet().stream() + .map(path -> { + if (path.startsWith(METADATA_DIR)) + return path.substring(METADATA_DIR.length()); + return CONTENT_DIRECTORY + path; + }) + .collect(Collectors.toSet()); + + for (var pathString : paths) { + zipOut.putNextEntry(new ZipEntry(pathString)); + var p = dir.resolve(pathString); + if (Files.isRegularFile(p)) zipOut.write(Files.readAllBytes(p)); + zipOut.closeEntry(); + } + } + + var data = Files.readAllBytes(zipPath); + Files.delete(zipPath); + + var asset = Type.MTLIB.getFactory().apply(addOnLib.getNamespace(), data); + addAsset(asset); + + + return AddOnLibrary.fromDto( + asset.getMD5Key(), + addOnLib, + mtsPropBuilder.build(), + eventPropBuilder.build(), + statSheetsBuilder.build(), + slashCommandsBuilder.build(), + pathAssetMap); } /** @@ -155,7 +238,6 @@ public AddOnLibrary importFromDirectory(Path dir) throws IOException { * @throws IOException if an error occurs while reading the asset. */ public AddOnLibrary importFromFile(File file) throws IOException { - var diiBuilder = AddOnLibraryDto.newBuilder(); try (var zip = new ZipFile(file)) { ZipEntry entry = zip.getEntry(LIBRARY_INFO_FILE); @@ -168,7 +250,7 @@ public AddOnLibrary importFromFile(File file) throws IOException { .merge(new InputStreamReader(zip.getInputStream(entry)), builder); // MT MacroScript properties - var pathAssetMap = processAssets(builder.getNamespace(), zip); + var pathAssetMap = processAssetsFromZip(builder.getNamespace(), zip); var mtsPropBuilder = MTScriptPropertiesDto.newBuilder(); ZipEntry mtsPropsZipEntry = zip.getEntry(MACROSCRIPT_PROPERTY_FILE); if (mtsPropsZipEntry != null) { @@ -207,7 +289,7 @@ public AddOnLibrary importFromFile(File file) throws IOException { } // Copy Metadata - addMetaData(builder.getNamespace(), zip, pathAssetMap); + addMetaDataFromZip(builder.getNamespace(), zip, pathAssetMap); var addOnLib = builder.build(); byte[] data = Files.readAllBytes(file.toPath()); @@ -252,7 +334,7 @@ public AddOnLibrary importFromClassPath(String path) throws IOException { * @param pathAssetMap the map of asset paths and asset details. * @throws IOException */ - private void addMetaData( + private void addMetaDataFromZip( String namespace, ZipFile zip, Map> pathAssetMap) throws IOException { var entries = zip.stream().filter(e -> !e.getName().contains("/")).toList(); @@ -269,6 +351,29 @@ private void addMetaData( } } + /** + * Adds the metadata from the add-on directory to the metadata directory. + * + * @param namespace The namespace of the add-on. + * @param dir The directory of the add-on. + * @param pathAssetMap The asset details output. + * @throws IOException If there is an error reading assets from the directory. + */ + private void addMetaDataFromDirectory(String namespace, Path dir, Map> pathAssetMap) + throws IOException { + var entries = Files.list(dir).filter(p -> !Files.isDirectory(p)).collect(Collectors.toSet()); + for (var entry : entries) { + var path = METADATA_DIR + entry.getFileName().toString(); + var bytes = Files.readAllBytes(entry); + + var mediaType = Asset.getMediaType(entry.getFileName().toString(), bytes); + + var asset = Type.fromMediaType(mediaType).getFactory().apply(namespace + "/" + path, bytes); + addAsset(asset); + pathAssetMap.put(path, Pair.with(asset.getMD5Key(), asset.getType())); + } + } + /** * Reads the assets from the add-on library and adds them to the asset manager. * @@ -277,7 +382,7 @@ private void addMetaData( * @return a map of asset paths and asset details. * @throws IOException if there is an error reading the assets from the add-on library. */ - private Map> processAssets(String namespace, ZipFile zip) + private Map> processAssetsFromZip(String namespace, ZipFile zip) throws IOException { var pathAssetMap = new HashMap>(); var entries = @@ -299,6 +404,37 @@ private Map> processAssets(String namespace, ZipFile return pathAssetMap; } + /** + * Reads the assets from a flat directory. This is primarily used for external libraries, such as + * development-mode libraries. + * @param namespace The namespace to classify assets under. + * @param dir The directory to process as an add-on. + * @return A map containing asset paths and details. + * @throws IOException If there is an error reading assets from the directory. + */ + private Map> processAssetsFromDirectory(String namespace, Path dir) throws IOException { + var pathAssetMap = new HashMap>(); + var contentDir = dir.resolve(CONTENT_DIRECTORY); + + // Empty libraries are still permitted. + if (!Files.exists(contentDir)) + return pathAssetMap; + + for (Path entry : FileUtil.listRecursively(contentDir).collect(Collectors.toSet())) { + if (Files.isDirectory(entry)) continue; + entry = dir.relativize(entry); + var pathString = entry.toString().substring(CONTENT_DIRECTORY.length()).replace('\\', '/'); + var bytes = Files.readAllBytes(dir.resolve(entry)); + + var mediaType = Asset.getMediaType(entry.toString(), bytes); + + var asset = Type.fromMediaType(mediaType).getFactory().apply(namespace + "/" + pathString, bytes); + addAsset(asset); + pathAssetMap.put(pathString, Pair.with(asset.getMD5Key(), asset.getType())); + } + return pathAssetMap; + } + /** * Adds the {@link Asset} to the {@link AssetManager} if it does not already exist. * diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java index cbd9f61181..5eb8ddce0d 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java @@ -272,4 +272,20 @@ public Path getExternalLibraryPath() { public void setExternalLibraryPath(Path path) { externalAddOnLibraryManager.setExternalLibraryPath(path); } + + /** + * Registers the add-on library as an external library. + * @param addOnLibrary The add-on library to register. + */ + public void registerExternalLibrary(AddOnLibrary addOnLibrary) { + externalAddOnLibraryManager.registerExternalAddOnLibrary(addOnLibrary); + } + + /** + * De-registers the add-on library with the given namespace. + * @param namespace The namespace of the library. + */ + public void deregisterExternalLibrary(String namespace) { + externalAddOnLibraryManager.deregisterExternalAddOnLibrary(namespace); + } } diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java index 2a5f5edf29..20e96f6485 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java @@ -18,12 +18,12 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; + +import net.rptools.maptool.events.MapToolEventBus; +import net.rptools.maptool.model.library.AddOnsRemovedEvent; import net.rptools.maptool.model.library.LibraryInfo; public class ExternalAddOnLibraryManager { @@ -82,17 +82,14 @@ public void registerExternalAddOnLibrary(AddOnLibrary addOnLibrary) { }); } - public void deregisterExternalAddOnLibrary(AddOnLibrary addOnLibrary) { - addOnLibrary - .getNamespace() - .thenAccept( - namespace -> { - namespaceLibraryMap.remove(namespace); - namespaceLibraryMap.remove(namespace); - addOnLibrary.getLibraryInfo().thenAccept(externalLibraryInfo::remove); - addOnLibraryManager.deregisterLibrary(namespace); - cacheExternalLibraryInfo(); - }); + public void deregisterExternalAddOnLibrary(String namespace) { + var removed = namespaceLibraryMap.remove(namespace.toLowerCase()); + if (removed != null) { + removed.cleanup(); + new MapToolEventBus() + .getMainEventBus() + .post(new AddOnsRemovedEvent(Set.of(removed.getLibraryInfo().join()))); + } } private void cacheExternalLibraryInfo() { @@ -221,8 +218,7 @@ private DirectoryWatcher createDirectoryWatcher() throws IOException { } case DELETE -> { if (event.path().relativize(externalLibraryPath).getNameCount() == 1) { - deregisterExternalAddOnLibrary( - namespaceLibraryMap.get(event.path().getFileName().toString())); + deregisterExternalAddOnLibrary(event.path().getFileName().toString()); } } case MODIFY -> { diff --git a/src/main/java/net/rptools/maptool/util/FileUtil.java b/src/main/java/net/rptools/maptool/util/FileUtil.java index 577db94ab4..71c6747257 100644 --- a/src/main/java/net/rptools/maptool/util/FileUtil.java +++ b/src/main/java/net/rptools/maptool/util/FileUtil.java @@ -21,6 +21,12 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import net.rptools.maptool.client.AppUtil; /** @@ -198,4 +204,29 @@ public static File cleanFileName(String path, String fileName, String extension) public static File cleanFileName(String fileName, String extension) { return cleanFileName(null, fileName, extension); } + + /** + * Uses {@link Files#list(Path)} to recursively list all paths from a directory. + * The result includes directories. + * @param dir The directory to list recursively. + * @return A stream with all files. + * @throws IOException if an I/O error occurs while opening the directory, or any of its recursive children. + */ + public static Stream listRecursively(Path dir) throws IOException { + var list = Files.list(dir).collect(Collectors.toSet()); + try { + return Stream.concat(list.stream(), list.stream().flatMap(p -> { + if (Files.isDirectory(p)) + try { + return listRecursively(p).map(p::resolve); + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; + }).filter(Objects::nonNull)); + } catch (RuntimeException e) { + // This is to bypass an uncaught exception in the above lambda that calls in place. + throw (IOException) e.getCause(); + } + } } From 213a9d27c493f12181f76aaa0fb4300275556709 Mon Sep 17 00:00:00 2001 From: Sparky200 Date: Sun, 26 May 2024 02:34:54 -0500 Subject: [PATCH 005/146] Clean imports --- .../maptool/client/ui/addon/AddOnLibrariesDialogView.java | 5 +---- .../maptool/model/library/addon/AddOnLibraryImporter.java | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index a5853f196e..fad4a3a137 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -48,7 +48,6 @@ import net.rptools.maptool.model.library.Library; import net.rptools.maptool.model.library.LibraryInfo; import net.rptools.maptool.model.library.LibraryManager; -import net.rptools.maptool.model.library.addon.AddOnLibrary; import net.rptools.maptool.model.library.addon.AddOnLibraryImporter; /** Dialog for managing add-on libraries. */ @@ -220,9 +219,7 @@ public void actionPerformed(ActionEvent e) { }); createAddonSkeletonButton.addActionListener( - e -> { - createAddonSkeleton(); - }); + e -> createAddonSkeleton()); enableExternalAddOnCheckBox.addActionListener( e -> { diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java index b2b03c4fa4..91e78abc4c 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java @@ -23,7 +23,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; From 15a642e0a7d3db6e72b0bbea323a0c29fa738a57 Mon Sep 17 00:00:00 2001 From: Craig Wisniewski Date: Wed, 28 Aug 2024 14:27:29 +0930 Subject: [PATCH 006/146] merge changes --- .../ui/addon/AddOnLibrariesDialogView.java | 7 +- .../maptool/model/library/LibraryManager.java | 12 ++-- .../library/addon/AddOnLibraryImporter.java | 65 ++++++++++--------- .../library/addon/AddOnLibraryManager.java | 2 + .../addon/ExternalAddOnLibraryManager.java | 5 +- .../net/rptools/maptool/util/FileUtil.java | 32 +++++---- 6 files changed, 66 insertions(+), 57 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index fad4a3a137..17b1fbae49 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -218,8 +218,7 @@ public void actionPerformed(ActionEvent e) { }); }); - createAddonSkeletonButton.addActionListener( - e -> createAddonSkeleton()); + createAddonSkeletonButton.addActionListener(e -> createAddonSkeleton()); enableExternalAddOnCheckBox.addActionListener( e -> { @@ -248,8 +247,8 @@ private void setExternalAddOnControlsEnabled(boolean selected) { private void createAddonSkeleton() { LibraryManager library = new LibraryManager(); try { - library.registerExternalAddOnLibrary(new AddOnLibraryImporter() - .importFromDirectory(Path.of(directoryTextField.getText()))); + library.registerExternalAddOnLibrary( + new AddOnLibraryImporter().importFromDirectory(Path.of(directoryTextField.getText()))); } catch (IOException e) { MapTool.showError("library.import.ioError", e); } diff --git a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java index 2c1339283f..1d7b354822 100644 --- a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java @@ -224,8 +224,8 @@ public boolean registerAddOnLibrary(AddOnLibrary addOn) { } /** - * Deregister the add-on in library associated with the specified namespace. - * This will deregister the add-on if it is external. + * Deregister the add-on in library associated with the specified namespace. This will deregister + * the add-on if it is external. * * @param namespace the namespace to deregister. */ @@ -258,15 +258,17 @@ public boolean reregisterAddOnLibrary(AddOnLibrary addOnLibrary) { /** * Register an add-on as an external add-on. + * * @param addOnLibrary The add-on to register. - * @return Whether the add-on was successfully registered. - * If the result is `false`, the reason will be logged as an error. + * @return Whether the add-on was successfully registered. If the result is `false`, the reason + * will be logged as an error. */ public boolean registerExternalAddOnLibrary(AddOnLibrary addOnLibrary) { try { addOnLibraryManager.registerExternalLibrary(addOnLibrary); if (MapTool.isHostingServer()) - MapTool.serverCommand().addAddOnLibrary(List.of(new TransferableAddOnLibrary(addOnLibrary))); + MapTool.serverCommand() + .addAddOnLibrary(List.of(new TransferableAddOnLibrary(addOnLibrary))); } catch (InterruptedException | ExecutionException e) { log.error("Error registering external add-on", e); return false; diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java index 91e78abc4c..20237a3788 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java @@ -154,9 +154,7 @@ public AddOnLibrary importFromDirectory(Path dir) throws IOException { throw new IOException(I18N.getText("library.error.addOn.noConfigFile", dir)); var builder = AddOnLibraryDto.newBuilder(); - JsonFormat.parser() - .ignoringUnknownFields() - .merge(Files.newBufferedReader(infoPath), builder); + JsonFormat.parser().ignoringUnknownFields().merge(Files.newBufferedReader(infoPath), builder); var pathAssetMap = processAssetsFromDirectory(builder.getNamespace(), dir); @@ -164,29 +162,29 @@ public AddOnLibrary importFromDirectory(Path dir) throws IOException { var mtsPropPath = dir.resolve(MACROSCRIPT_PROPERTY_FILE); if (Files.exists(mtsPropPath)) JsonFormat.parser() - .ignoringUnknownFields() - .merge(Files.newBufferedReader(mtsPropPath), mtsPropBuilder); + .ignoringUnknownFields() + .merge(Files.newBufferedReader(mtsPropPath), mtsPropBuilder); var eventPropBuilder = AddOnLibraryEventsDto.newBuilder(); var eventPropPath = dir.resolve(EVENT_PROPERTY_FILE); if (Files.exists(eventPropPath)) JsonFormat.parser() - .ignoringUnknownFields() - .merge(Files.newBufferedReader(eventPropPath), eventPropBuilder); + .ignoringUnknownFields() + .merge(Files.newBufferedReader(eventPropPath), eventPropBuilder); var statSheetsBuilder = AddOnStatSheetsDto.newBuilder(); var statSheetsPath = dir.resolve(STATS_SHEET_FILE); if (Files.exists(statSheetsPath)) JsonFormat.parser() - .ignoringUnknownFields() - .merge(Files.newBufferedReader(statSheetsPath), statSheetsBuilder); + .ignoringUnknownFields() + .merge(Files.newBufferedReader(statSheetsPath), statSheetsBuilder); var slashCommandsBuilder = AddonSlashCommandsDto.newBuilder(); var slashCommandsPath = dir.resolve(SLASH_COMMAND_FILE); if (Files.exists(slashCommandsPath)) JsonFormat.parser() - .ignoringUnknownFields() - .merge(Files.newBufferedReader(slashCommandsPath), slashCommandsBuilder); + .ignoringUnknownFields() + .merge(Files.newBufferedReader(slashCommandsPath), slashCommandsBuilder); addMetaDataFromDirectory(builder.getNamespace(), dir, pathAssetMap); @@ -195,13 +193,15 @@ public AddOnLibrary importFromDirectory(Path dir) throws IOException { var addOnLib = builder.build(); var zipPath = Files.createTempFile(builder.getNamespace(), null); - try (var zipOut = new ZipOutputStream(Files.newOutputStream(zipPath, StandardOpenOption.WRITE))) { - var paths = pathAssetMap.keySet().stream() - .map(path -> { - if (path.startsWith(METADATA_DIR)) - return path.substring(METADATA_DIR.length()); - return CONTENT_DIRECTORY + path; - }) + try (var zipOut = + new ZipOutputStream(Files.newOutputStream(zipPath, StandardOpenOption.WRITE))) { + var paths = + pathAssetMap.keySet().stream() + .map( + path -> { + if (path.startsWith(METADATA_DIR)) return path.substring(METADATA_DIR.length()); + return CONTENT_DIRECTORY + path; + }) .collect(Collectors.toSet()); for (var pathString : paths) { @@ -218,15 +218,14 @@ public AddOnLibrary importFromDirectory(Path dir) throws IOException { var asset = Type.MTLIB.getFactory().apply(addOnLib.getNamespace(), data); addAsset(asset); - return AddOnLibrary.fromDto( - asset.getMD5Key(), - addOnLib, - mtsPropBuilder.build(), - eventPropBuilder.build(), - statSheetsBuilder.build(), - slashCommandsBuilder.build(), - pathAssetMap); + asset.getMD5Key(), + addOnLib, + mtsPropBuilder.build(), + eventPropBuilder.build(), + statSheetsBuilder.build(), + slashCommandsBuilder.build(), + pathAssetMap); } /** @@ -358,8 +357,8 @@ private void addMetaDataFromZip( * @param pathAssetMap The asset details output. * @throws IOException If there is an error reading assets from the directory. */ - private void addMetaDataFromDirectory(String namespace, Path dir, Map> pathAssetMap) - throws IOException { + private void addMetaDataFromDirectory( + String namespace, Path dir, Map> pathAssetMap) throws IOException { var entries = Files.list(dir).filter(p -> !Files.isDirectory(p)).collect(Collectors.toSet()); for (var entry : entries) { var path = METADATA_DIR + entry.getFileName().toString(); @@ -406,18 +405,19 @@ private Map> processAssetsFromZip(String namespace, Z /** * Reads the assets from a flat directory. This is primarily used for external libraries, such as * development-mode libraries. + * * @param namespace The namespace to classify assets under. * @param dir The directory to process as an add-on. * @return A map containing asset paths and details. * @throws IOException If there is an error reading assets from the directory. */ - private Map> processAssetsFromDirectory(String namespace, Path dir) throws IOException { + private Map> processAssetsFromDirectory(String namespace, Path dir) + throws IOException { var pathAssetMap = new HashMap>(); var contentDir = dir.resolve(CONTENT_DIRECTORY); // Empty libraries are still permitted. - if (!Files.exists(contentDir)) - return pathAssetMap; + if (!Files.exists(contentDir)) return pathAssetMap; for (Path entry : FileUtil.listRecursively(contentDir).collect(Collectors.toSet())) { if (Files.isDirectory(entry)) continue; @@ -427,7 +427,8 @@ private Map> processAssetsFromDirectory(String namesp var mediaType = Asset.getMediaType(entry.toString(), bytes); - var asset = Type.fromMediaType(mediaType).getFactory().apply(namespace + "/" + pathString, bytes); + var asset = + Type.fromMediaType(mediaType).getFactory().apply(namespace + "/" + pathString, bytes); addAsset(asset); pathAssetMap.put(pathString, Pair.with(asset.getMD5Key(), asset.getType())); } diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java index 5eb8ddce0d..159e373c65 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java @@ -275,6 +275,7 @@ public void setExternalLibraryPath(Path path) { /** * Registers the add-on library as an external library. + * * @param addOnLibrary The add-on library to register. */ public void registerExternalLibrary(AddOnLibrary addOnLibrary) { @@ -283,6 +284,7 @@ public void registerExternalLibrary(AddOnLibrary addOnLibrary) { /** * De-registers the add-on library with the given namespace. + * * @param namespace The namespace of the library. */ public void deregisterExternalLibrary(String namespace) { diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java index 20e96f6485..3dc1ba5650 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java @@ -21,7 +21,6 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; - import net.rptools.maptool.events.MapToolEventBus; import net.rptools.maptool.model.library.AddOnsRemovedEvent; import net.rptools.maptool.model.library.LibraryInfo; @@ -87,8 +86,8 @@ public void deregisterExternalAddOnLibrary(String namespace) { if (removed != null) { removed.cleanup(); new MapToolEventBus() - .getMainEventBus() - .post(new AddOnsRemovedEvent(Set.of(removed.getLibraryInfo().join()))); + .getMainEventBus() + .post(new AddOnsRemovedEvent(Set.of(removed.getLibraryInfo().join()))); } } diff --git a/src/main/java/net/rptools/maptool/util/FileUtil.java b/src/main/java/net/rptools/maptool/util/FileUtil.java index 71c6747257..352b017b4d 100644 --- a/src/main/java/net/rptools/maptool/util/FileUtil.java +++ b/src/main/java/net/rptools/maptool/util/FileUtil.java @@ -26,7 +26,6 @@ import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; - import net.rptools.maptool.client.AppUtil; /** @@ -206,24 +205,31 @@ public static File cleanFileName(String fileName, String extension) { } /** - * Uses {@link Files#list(Path)} to recursively list all paths from a directory. - * The result includes directories. + * Uses {@link Files#list(Path)} to recursively list all paths from a directory. The result + * includes directories. + * * @param dir The directory to list recursively. * @return A stream with all files. - * @throws IOException if an I/O error occurs while opening the directory, or any of its recursive children. + * @throws IOException if an I/O error occurs while opening the directory, or any of its recursive + * children. */ public static Stream listRecursively(Path dir) throws IOException { var list = Files.list(dir).collect(Collectors.toSet()); try { - return Stream.concat(list.stream(), list.stream().flatMap(p -> { - if (Files.isDirectory(p)) - try { - return listRecursively(p).map(p::resolve); - } catch (IOException e) { - throw new RuntimeException(e); - } - return null; - }).filter(Objects::nonNull)); + return Stream.concat( + list.stream(), + list.stream() + .flatMap( + p -> { + if (Files.isDirectory(p)) + try { + return listRecursively(p).map(p::resolve); + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; + }) + .filter(Objects::nonNull)); } catch (RuntimeException e) { // This is to bypass an uncaught exception in the above lambda that calls in place. throw (IOException) e.getCause(); From 68dcb5cc7502134a8669d5e473eee9e8102a8044 Mon Sep 17 00:00:00 2001 From: Craig Wisniewski Date: Wed, 28 Aug 2024 17:10:20 +0930 Subject: [PATCH 007/146] add updates to the add-on dialog --- .../maptool/client/AppPreferences.java | 45 +++++++++++++++++++ .../ui/addon/AddOnLibrariesDialogView.form | 30 ++++++++++--- .../ui/addon/AddOnLibrariesDialogView.java | 15 +++++++ .../library/addon/AddOnLibraryManager.java | 6 +++ .../addon/ExternalAddOnLibraryManager.java | 28 +++++++++--- .../rptools/maptool/language/i18n.properties | 2 + 6 files changed, 114 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/AppPreferences.java b/src/main/java/net/rptools/maptool/client/AppPreferences.java index 298b30be10..7be9d27e12 100644 --- a/src/main/java/net/rptools/maptool/client/AppPreferences.java +++ b/src/main/java/net/rptools/maptool/client/AppPreferences.java @@ -629,6 +629,12 @@ public static int getFogOverlayOpacity() { /** The configuration key for specifying the width of the border around map labels for tokens. */ private static final String KEY_MAP_LABEL_SHOW_BORDER = "mapLabelShowBorder"; + /** The configuration key for specifying if external add-on libraries are enabled. */ + private static final String KEY_EXTERNAL_ADD_ON_LIBRARIES = "externalAddOnLibraries"; + + /** The configuration key for specifying the path to external add-on libraries. */ + private static final String KEY_EXTERNAL_ADD_ON_LIBRARIES_PATH = "externalAddOnLibrariesPath"; + /** The default background color for the NPC map label. */ private static final Color DEFAULT_NPC_MAP_LABEL_BG_COLOR = Color.LIGHT_GRAY; @@ -669,6 +675,9 @@ public static int getFogOverlayOpacity() { /** The default border arc for token map labels. */ private static final boolean DEFAULT_MAP_LABEL_SHOW_BORDER = true; + /** The default value for enabling external add-on libraries. */ + private static final boolean DEFAULT_EXTERNAL_ADDON_LIBRARIES = false; + public static void setHaloLineWidth(int size) { prefs.putInt(KEY_HALO_LINE_WIDTH, size); } @@ -1964,4 +1973,40 @@ public static boolean getShowMapLabelBorder() { public static void setShowMapLabelBorder(boolean show) { prefs.putBoolean(KEY_MAP_LABEL_SHOW_BORDER, show); } + + /** + * Returns the value of the external add-on library mode for add-on development. + * + * @return {@code true} if the developer mode is enabled, {@code false} otherwise. + */ + public static boolean getExternalLibraryManagerEnabled() { + return prefs.getBoolean(KEY_EXTERNAL_ADD_ON_LIBRARIES, DEFAULT_EXTERNAL_ADDON_LIBRARIES); + } + + /** + * Sets the external add-on library mode for add on development. + * + * @param mode {@code true} to enable the developer mode, {@code false} to disable it. + */ + public static void setExternalLibraryManagerEnabled(boolean mode) { + prefs.putBoolean(KEY_EXTERNAL_ADD_ON_LIBRARIES, mode); + } + + /** + * Returns the path to the external add-on libraries. + * + * @return the path to the external add-on libraries. + */ + public static String getExternalAddOnLibrariesPath() { + return prefs.get(KEY_EXTERNAL_ADD_ON_LIBRARIES_PATH, null); + } + + /** + * Sets the path to the external add-on libraries. + * + * @param path the path to the external add-on libraries. + */ + public static void setExternalAddOnLibrariesPath(String path) { + prefs.put(KEY_EXTERNAL_ADD_ON_LIBRARIES_PATH, path); + } } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form index 801bad3aff..8b7ead50a2 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form @@ -283,7 +283,7 @@ - + @@ -293,7 +293,7 @@ - + @@ -301,26 +301,42 @@ - + - + + + + + + + + + - + - + - + + + + + + + + + diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index 17b1fbae49..1679449016 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -41,6 +41,7 @@ import javax.swing.KeyStroke; import net.rptools.maptool.client.AppActions.MapPreviewFileChooser; import net.rptools.maptool.client.AppConstants; +import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.ui.JLabelHyperLinkListener; import net.rptools.maptool.client.ui.ViewAssetDialog; @@ -226,19 +227,33 @@ public void actionPerformed(ActionEvent e) { directoryTextField.setEnabled(enableExternalAddOnCheckBox.isSelected()); new LibraryManager() .setExternalLibrariesEnabled(enableExternalAddOnCheckBox.isSelected()); + AppPreferences.setExternalLibraryManagerEnabled(enableExternalAddOnCheckBox.isSelected()); }); browseButton.addActionListener( e -> { JFileChooser chooser = new JFileChooser(); chooser.setDialogTitle(I18N.getText("library.dialog.import.title")); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.showOpenDialog(MapTool.getFrame()); + if (chooser.getSelectedFile() != null) { + directoryTextField.setText(chooser.getSelectedFile().getAbsolutePath()); + AppPreferences.setExternalAddOnLibrariesPath( + chooser.getSelectedFile().getAbsolutePath()); + } }); LibraryManager libraryManager = new LibraryManager(); enableExternalAddOnCheckBox.setSelected(libraryManager.externalLibrariesEnabled()); + directoryTextField.setText(AppPreferences.getExternalAddOnLibrariesPath()); setExternalAddOnControlsEnabled(enableExternalAddOnCheckBox.isSelected()); + if (enableExternalAddOnCheckBox.isSelected()) { + refreshLibraries(); + } } + private void refreshLibraries() {} + private void setExternalAddOnControlsEnabled(boolean selected) { externalAddonTable.setEnabled(enableExternalAddOnCheckBox.isSelected()); browseButton.setEnabled(enableExternalAddOnCheckBox.isSelected()); diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java index 159e373c65..c6d81d4c7f 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java @@ -24,6 +24,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.events.MapToolEventBus; import net.rptools.maptool.model.library.AddOnsAddedEvent; import net.rptools.maptool.model.library.AddOnsRemovedEvent; @@ -211,6 +212,11 @@ public CompletableFuture> getLegacyEventTargets(String eventName) { /** Initializes the add-on library manager. */ public void init() { externalAddOnLibraryManager = new ExternalAddOnLibraryManager(this); + String path = AppPreferences.getExternalAddOnLibrariesPath(); + if (path != null && !path.isEmpty()) { + externalAddOnLibraryManager.setExternalLibraryPath(Path.of(path)); + externalAddOnLibraryManager.setEnabled(AppPreferences.getExternalLibraryManagerEnabled()); + } } /** diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java index 3dc1ba5650..67240588bf 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java @@ -15,6 +15,7 @@ package net.rptools.maptool.model.library.addon; import io.methvin.watcher.DirectoryWatcher; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -27,7 +28,7 @@ public class ExternalAddOnLibraryManager { - /** The add-on library manager that is used to register the add-on libraries. */ + /** The add-on library manager that is used to register the external add-on libraries. */ private final AddOnLibraryManager addOnLibraryManager; /** The add-on libraries that are registered. */ @@ -67,10 +68,10 @@ public void registerExternalAddOnLibrary(AddOnLibrary addOnLibrary) { .thenAccept( namespace -> { if (addOnLibraryManager.isNamespaceRegistered(namespace)) { - addOnLibraryManager.deregisterLibrary(namespace); + addOnLibraryManager.deregisterLibrary(namespace); // TODO: CDW This should only happen on import } namespaceLibraryMap.put(namespace, addOnLibrary); - addOnLibraryManager.registerLibrary(addOnLibrary); + addOnLibraryManager.registerLibrary(addOnLibrary); // TODO: CDW This should only happen on import addOnLibrary .getLibraryInfo() .thenAccept( @@ -87,7 +88,7 @@ public void deregisterExternalAddOnLibrary(String namespace) { removed.cleanup(); new MapToolEventBus() .getMainEventBus() - .post(new AddOnsRemovedEvent(Set.of(removed.getLibraryInfo().join()))); + .post(new AddOnsRemovedEvent(Set.of(removed.getLibraryInfo().join()))); // TODO: CDW This should only happen on import } } @@ -103,7 +104,7 @@ private void cacheExternalLibraryInfo() { public void refreshExternalAddOnLibrary(String namespace, Path path) { try { var lib = new AddOnLibraryImporter().importFromDirectory(path); - addOnLibraryManager.replaceLibrary(lib); + addOnLibraryManager.replaceLibrary(lib); // TODO: CDW This should only happen on import } catch (IOException e) { throw new RuntimeException(e); // TODO: CDW } @@ -184,6 +185,7 @@ private void stopWatching() { private void startWatching() { try { lock.lock(); + refreshAll(); if (isEnabled && externalLibraryPath != null && Files.exists(externalLibraryPath)) { if (directoryWatcher != null) { directoryWatcher.watchAsync(); @@ -199,6 +201,20 @@ private void startWatching() { } } + private void refreshAll() { + File[] directories = externalLibraryPath.toFile().listFiles(File::isDirectory); + if (directories != null) { + for (File directory : directories) { + try { + var lib = new AddOnLibraryImporter().importFromDirectory(directory.toPath()); + registerExternalAddOnLibrary(lib); + } catch (IOException e) { + throw new RuntimeException(e); // TODO: CDW + } + } + } + } + private DirectoryWatcher createDirectoryWatcher() throws IOException { return DirectoryWatcher.builder() .path(externalLibraryPath) @@ -231,3 +247,5 @@ private DirectoryWatcher createDirectoryWatcher() throws IOException { .build(); } } + +// TODO: CDW changes to the add on library directory will not automatically update the add on library that MT uses \ No newline at end of file diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index e7c0168922..7809cd125e 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2983,7 +2983,9 @@ library.dialog.addon.subdir = Sub Directory library.dialog.addon.refresh = Refresh library.dialog.addon.createNew = Create New Add-On library.dialog.addon.export = Export Add-On +library.dialog.addon.import = Import Add-On library.dialog.directory.label = Add On Directory +library.dialog.directory.refresh = Refresh Directory Contents From 2dcfccce4687091899f5b67255faf9db0caea926 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Thu, 29 Aug 2024 11:16:12 +0930 Subject: [PATCH 008/146] Make some adjustments for file events --- .../library/addon/AddOnLibraryManager.java | 8 ++ .../addon/ExternalAddOnLibraryManager.java | 76 +++++++++++++++++-- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java index c6d81d4c7f..f56a762c54 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java @@ -296,4 +296,12 @@ public void registerExternalLibrary(AddOnLibrary addOnLibrary) { public void deregisterExternalLibrary(String namespace) { externalAddOnLibraryManager.deregisterExternalAddOnLibrary(namespace); } + + /** + * Makes the external library with the given namespace available to MapTool. + * @param namespace The namespace of the library. + */ + public void makeExternalLibraryAvailable(String namespace) { + externalAddOnLibraryManager.makeAvailable(namespace); + } } diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java index 67240588bf..d70c70989b 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java @@ -26,6 +26,11 @@ import net.rptools.maptool.model.library.AddOnsRemovedEvent; import net.rptools.maptool.model.library.LibraryInfo; +/** + * Manages the external add-on libraries that are baked by the file system. This manager will watch + * the external add-on library directory for changes and will update the add-on libraries + * available but will not automatically update the add-on libraries that MapTool has loaded. + */ public class ExternalAddOnLibraryManager { /** The add-on library manager that is used to register the external add-on libraries. */ @@ -62,6 +67,10 @@ public ExternalAddOnLibraryManager(AddOnLibraryManager addOnLibraryManager) { externalLibraryInfo = Collections.synchronizedList(new ArrayList<>()); } + /** + * Registers an external add-on library. + * @param addOnLibrary the add-on library to register. + */ public void registerExternalAddOnLibrary(AddOnLibrary addOnLibrary) { addOnLibrary .getNamespace() @@ -82,16 +91,17 @@ public void registerExternalAddOnLibrary(AddOnLibrary addOnLibrary) { }); } + /** + * Deregisters an external add-on library. + * @param namespace the namespace of the add-on library to deregister. + */ public void deregisterExternalAddOnLibrary(String namespace) { - var removed = namespaceLibraryMap.remove(namespace.toLowerCase()); - if (removed != null) { - removed.cleanup(); - new MapToolEventBus() - .getMainEventBus() - .post(new AddOnsRemovedEvent(Set.of(removed.getLibraryInfo().join()))); // TODO: CDW This should only happen on import - } + namespaceLibraryMap.remove(namespace.toLowerCase()); } + /** + * Caches the external library information. + */ private void cacheExternalLibraryInfo() { externalLibraryInfo.clear(); var infoList = @@ -101,6 +111,11 @@ private void cacheExternalLibraryInfo() { externalLibraryInfo.addAll(infoList); } + /** + * Refreshes an external add-on library. + * @param namespace the namespace of the add-on library to refresh. + * @param path the path to the add-on library. + */ public void refreshExternalAddOnLibrary(String namespace, Path path) { try { var lib = new AddOnLibraryImporter().importFromDirectory(path); @@ -110,10 +125,18 @@ public void refreshExternalAddOnLibrary(String namespace, Path path) { } } + /** + * Gets the external libraries that have been registered. + * @return the external libraries. + */ public List getLibraries() { return externalLibraryInfo; } + /** + * Is the external add-on library manager enabled. + * @return {@code true} if the external add-on library manager is enabled. + */ public boolean isEnabled() { try { lock.lock(); @@ -123,6 +146,10 @@ public boolean isEnabled() { } } + /** + * Sets the enabled state of the external add-on library manager. + * @param enabled the enabled state. + */ public void setEnabled(boolean enabled) { try { lock.lock(); @@ -139,6 +166,10 @@ public void setEnabled(boolean enabled) { } } + /** + * Gets the path to the external add-on libraries. + * @return the path to the external add-on libraries. + */ public Path getExternalLibraryPath() { try { lock.lock(); @@ -148,6 +179,10 @@ public Path getExternalLibraryPath() { } } + /** + * Sets the path to the external add-on libraries. + * @param path the path to the external add-on libraries. + */ public void setExternalLibraryPath(Path path) { if (path != null && path.equals(externalLibraryPath)) { return; @@ -166,6 +201,9 @@ public void setExternalLibraryPath(Path path) { } } + /** + * Stops watching the external add-on library directory. + */ private void stopWatching() { try { lock.lock(); @@ -182,6 +220,9 @@ private void stopWatching() { } } + /** + * Starts watching the external add-on library directory. + */ private void startWatching() { try { lock.lock(); @@ -201,6 +242,9 @@ private void startWatching() { } } + /** + * Refreshes all the external add-on libraries. + */ private void refreshAll() { File[] directories = externalLibraryPath.toFile().listFiles(File::isDirectory); if (directories != null) { @@ -215,6 +259,24 @@ private void refreshAll() { } } + /** + * Makes the add-on library with the specified namespace available to MapTool. + * @param namespace the namespace of the add-on library to make available. + */ + public void makeAvailable(String namespace) { + if (isEnabled) { + var lib = namespaceLibraryMap.get(namespace); + if (lib != null) { + addOnLibraryManager.registerLibrary(lib); + } + } + } + + /** + * Stops the add-on library with the specified namespace from being available to MapTool. + * @return the namespace of the add-on library to stop being available. + * @throws IOException if an error occurs. + */ private DirectoryWatcher createDirectoryWatcher() throws IOException { return DirectoryWatcher.builder() .path(externalLibraryPath) From dbe05a5cad003eff4bbed452d0f3a2816e2aebbb Mon Sep 17 00:00:00 2001 From: Craig Wisniewski Date: Thu, 29 Aug 2024 17:39:36 +0930 Subject: [PATCH 009/146] fix up external lib detection --- .../ui/addon/AddOnLibrariesDialogView.form | 2 +- .../ui/addon/AddOnLibrariesDialogView.java | 90 ++++----- .../addon/ExternalAddOnImportCellEditor.java | 62 ++++++ .../ExternalAddOnLibrariesTableModel.java | 34 ++-- .../maptool/model/library/LibraryManager.java | 43 ++-- .../library/addon/AddOnLibraryManager.java | 26 +-- .../addon/ExternalAddOnLibraryManager.java | 189 ++++++++++++------ .../library/addon/ExternalLibraryInfo.java | 33 +++ .../rptools/maptool/language/i18n.properties | 2 + 9 files changed, 314 insertions(+), 167 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java create mode 100644 src/main/java/net/rptools/maptool/model/library/addon/ExternalLibraryInfo.java diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form index 8b7ead50a2..e6af53d67f 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form @@ -307,7 +307,7 @@ - + diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index 1679449016..e7adad806f 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -16,15 +16,12 @@ import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; import java.net.URI; -import java.nio.file.Path; import java.util.Optional; import java.util.concurrent.ExecutionException; import javax.swing.JButton; @@ -50,6 +47,7 @@ import net.rptools.maptool.model.library.LibraryInfo; import net.rptools.maptool.model.library.LibraryManager; import net.rptools.maptool.model.library.addon.AddOnLibraryImporter; +import net.rptools.maptool.model.library.addon.ExternalLibraryInfo; /** Dialog for managing add-on libraries. */ public class AddOnLibrariesDialogView extends JDialog { @@ -78,7 +76,7 @@ public class AddOnLibrariesDialogView extends JDialog { private JButton createAddonSkeletonButton; private JTextField directoryTextField; private JButton browseButton; - private JButton exportAddOn; + private JButton exportAddOnButton; private LibraryInfo selectedAddOn; @@ -92,6 +90,10 @@ public AddOnLibrariesDialogView() { externalAddonTable.setModel(new ExternalAddOnLibrariesTableModel()); externalAddonTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + externalAddonTable.setDefaultRenderer( + ExternalLibraryInfo.class, new ExternalAddOnImportCellEditor()); + externalAddonTable.setDefaultEditor( + ExternalLibraryInfo.class, new ExternalAddOnImportCellEditor()); buttonRemove.setEnabled(false); addOnLibraryTable @@ -143,11 +145,7 @@ public void windowClosing(WindowEvent e) { // call onClose() on ESCAPE contentPane.registerKeyboardAction( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - onClose(); - } - }, + e -> onClose(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); @@ -182,42 +180,40 @@ public void actionPerformed(ActionEvent e) { }); copyThemeCSS.addActionListener( - e -> { - new LibraryManager() - .getLibrary(AppConstants.MT_BUILTIN_ADD_ON_NAMESPACE) - .ifPresent( - library -> { - URI uri = URI.create(AppConstants.MT_THEME_CSS); - String themeCss = null; - try { - themeCss = library.readAsString(uri.toURL()).get(); - } catch (InterruptedException | ExecutionException | IOException ex) { - throw new RuntimeException(ex); - } - Toolkit.getDefaultToolkit() - .getSystemClipboard() - .setContents(new StringSelection(themeCss), null); - }); - }); + e -> + new LibraryManager() + .getLibrary(AppConstants.MT_BUILTIN_ADD_ON_NAMESPACE) + .ifPresent( + library -> { + URI uri = URI.create(AppConstants.MT_THEME_CSS); + String themeCss = null; + try { + themeCss = library.readAsString(uri.toURL()).get(); + } catch (InterruptedException | ExecutionException | IOException ex) { + throw new RuntimeException(ex); + } + Toolkit.getDefaultToolkit() + .getSystemClipboard() + .setContents(new StringSelection(themeCss), null); + })); copyStatSheetThemeButton.addActionListener( - e -> { - new LibraryManager() - .getLibrary(AppConstants.MT_BUILTIN_ADD_ON_NAMESPACE) - .ifPresent( - library -> { - URI uri = URI.create(AppConstants.MT_THEME_STAT_SHEET_CSS); - String themeCss = null; - try { - themeCss = library.readAsString(uri.toURL()).get(); - } catch (InterruptedException | ExecutionException | IOException ex) { - throw new RuntimeException(ex); - } - Toolkit.getDefaultToolkit() - .getSystemClipboard() - .setContents(new StringSelection(themeCss), null); - }); - }); + e -> + new LibraryManager() + .getLibrary(AppConstants.MT_BUILTIN_ADD_ON_NAMESPACE) + .ifPresent( + library -> { + URI uri = URI.create(AppConstants.MT_THEME_STAT_SHEET_CSS); + String themeCss = null; + try { + themeCss = library.readAsString(uri.toURL()).get(); + } catch (InterruptedException | ExecutionException | IOException ex) { + throw new RuntimeException(ex); + } + Toolkit.getDefaultToolkit() + .getSystemClipboard() + .setContents(new StringSelection(themeCss), null); + })); createAddonSkeletonButton.addActionListener(e -> createAddonSkeleton()); @@ -255,18 +251,20 @@ public void actionPerformed(ActionEvent e) { private void refreshLibraries() {} private void setExternalAddOnControlsEnabled(boolean selected) { - externalAddonTable.setEnabled(enableExternalAddOnCheckBox.isSelected()); - browseButton.setEnabled(enableExternalAddOnCheckBox.isSelected()); + externalAddonTable.setEnabled(selected); + browseButton.setEnabled(selected); } private void createAddonSkeleton() { + // TODO: CDW - Implement + /* LibraryManager library = new LibraryManager(); try { library.registerExternalAddOnLibrary( new AddOnLibraryImporter().importFromDirectory(Path.of(directoryTextField.getText()))); } catch (IOException e) { MapTool.showError("library.import.ioError", e); - } + }**/ } /** diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java new file mode 100644 index 0000000000..b0256e6757 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java @@ -0,0 +1,62 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.ui.addon; + +import java.awt.*; +import javax.swing.*; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; +import net.rptools.maptool.model.library.addon.ExternalLibraryInfo; + +public class ExternalAddOnImportCellEditor extends AbstractCellEditor + implements TableCellEditor, TableCellRenderer { + + private final JButton button = new JButton(); + private ExternalLibraryInfo info; + + public ExternalAddOnImportCellEditor() { + button.addActionListener( + e -> { + System.out.println("Button clicked: " + info.namespace()); + stopCellEditing(); + }); + } + + @Override + public Component getTableCellEditorComponent( + JTable table, Object value, boolean isSelected, int row, int column) { + return getButton(table, value, isSelected, false, row, column); + } + + @Override + public Object getCellEditorValue() { + return info; + } + + @Override + public Component getTableCellRendererComponent( + JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + return getButton(table, value, isSelected, hasFocus, row, column); + } + + private Component getButton( + JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + button.setEnabled(true); + info = (ExternalLibraryInfo) value; + button.setText(info.namespace()); + + return button; + } +} diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java index c14f6b471c..c63247a195 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java @@ -15,11 +15,10 @@ package net.rptools.maptool.client.ui.addon; import java.util.List; -import javax.swing.JButton; import javax.swing.table.AbstractTableModel; import net.rptools.maptool.language.I18N; -import net.rptools.maptool.model.library.LibraryInfo; import net.rptools.maptool.model.library.LibraryManager; +import net.rptools.maptool.model.library.addon.ExternalLibraryInfo; public class ExternalAddOnLibrariesTableModel extends AbstractTableModel { @@ -33,24 +32,27 @@ public int getColumnCount() { return 5; } + @Override + public Class getColumnClass(int columnIndex) { + return columnIndex == 4 ? ExternalLibraryInfo.class : String.class; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return columnIndex == 4; + } + @Override public Object getValueAt(int rowIndex, int columnIndex) { - List addons = new LibraryManager().getExternalAddOnLibraries(); + List addons = new LibraryManager().getExternalAddOnLibraries(); + var info = addons.get(rowIndex); return switch (columnIndex) { - case 0 -> addons.get(rowIndex).name(); - case 1 -> addons.get(rowIndex).version(); - case 2 -> addons.get(rowIndex).namespace(); - case 3 -> addons.get(rowIndex).backingDirectory(); - case 4 -> { - var button = new JButton(); - button.setText("library.dialog.addon.refresh"); - button.addActionListener( - e -> { - refresh(rowIndex); - }); - yield button; - } + case 0 -> info.libraryInfo().name(); + case 1 -> info.libraryInfo().version(); + case 2 -> info.libraryInfo().namespace(); + case 3 -> info.libraryInfo().backingDirectory(); + case 4 -> info; default -> null; }; } diff --git a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java index 1d7b354822..6bc55be813 100644 --- a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java @@ -27,11 +27,7 @@ import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.MapToolMacroContext; import net.rptools.maptool.events.MapToolEventBus; -import net.rptools.maptool.model.library.addon.AddOnLibrary; -import net.rptools.maptool.model.library.addon.AddOnLibraryData; -import net.rptools.maptool.model.library.addon.AddOnLibraryManager; -import net.rptools.maptool.model.library.addon.AddOnSlashCommandManager; -import net.rptools.maptool.model.library.addon.TransferableAddOnLibrary; +import net.rptools.maptool.model.library.addon.*; import net.rptools.maptool.model.library.builtin.BuiltInLibraryManager; import net.rptools.maptool.model.library.proto.AddOnLibraryListDto; import net.rptools.maptool.model.library.token.LibraryTokenManager; @@ -87,7 +83,7 @@ public static void init() { * * @return the list of external add-on libraries. */ - public List getExternalAddOnLibraries() { + public List getExternalAddOnLibraries() { return addOnLibraryManager.getExternalAddOnLibraries(); } @@ -208,7 +204,7 @@ public boolean addOnLibraryExists(String namespace) { /** * Register and add-on library. * - * @param addOn the Add On to register. + * @param addOn the Add-On to register. */ public boolean registerAddOnLibrary(AddOnLibrary addOn) { try { @@ -237,7 +233,7 @@ public void deregisterAddOnLibrary(String namespace) { } /** - * Register a add-on in library, replacing any existing library. + * Register an add-on in library, replacing any existing library. * * @param addOnLibrary the add-on in library to register. */ @@ -257,23 +253,23 @@ public boolean reregisterAddOnLibrary(AddOnLibrary addOnLibrary) { } /** - * Register an add-on as an external add-on. + * Register an add-on as an external add-on. This will not make the add-on available to + * MapTool. To make the add-on available to MapTool, use {@link #importFromExternal(String)} * - * @param addOnLibrary The add-on to register. - * @return Whether the add-on was successfully registered. If the result is `false`, the reason - * will be logged as an error. + * @param path The path of the add-on to register. */ - public boolean registerExternalAddOnLibrary(AddOnLibrary addOnLibrary) { - try { - addOnLibraryManager.registerExternalLibrary(addOnLibrary); - if (MapTool.isHostingServer()) - MapTool.serverCommand() - .addAddOnLibrary(List.of(new TransferableAddOnLibrary(addOnLibrary))); - } catch (InterruptedException | ExecutionException e) { - log.error("Error registering external add-on", e); - return false; - } - return true; + public void registerExternalAddOnLibrary(Path path) { + addOnLibraryManager.registerExternalLibrary(path); + } + + /** + * Import an add-on from an external source. This will make the add-on available to MapTool. + * Importing an updated version of an add-on will replace the existing add-on. + * + * @param namespace The namespace of the add-on to import. + */ + public void importFromExternal(String namespace) { + addOnLibraryManager.importFromExternal(namespace); } /** @@ -409,6 +405,7 @@ public void removeAddOnLibrary(String namespace) { * initialization next time they are added. */ public void removeAddOnLibraries() { + for (var lib : addOnLibraryManager.getLibraries()) { lib.getLibraryData() .thenAccept( diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java index f56a762c54..93ee7559fd 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java @@ -29,7 +29,6 @@ import net.rptools.maptool.model.library.AddOnsAddedEvent; import net.rptools.maptool.model.library.AddOnsRemovedEvent; import net.rptools.maptool.model.library.Library; -import net.rptools.maptool.model.library.LibraryInfo; import net.rptools.maptool.model.library.proto.AddOnLibraryDto; import net.rptools.maptool.model.library.proto.AddOnLibraryListDto; import net.rptools.maptool.model.library.proto.AddOnLibraryListDto.AddOnLibraryEntryDto; @@ -186,7 +185,7 @@ public void removeAllLibraries() { .map(CompletableFuture::join) .collect(Collectors.toSet()); - if (libs.size() > 0) { + if (!libs.isEmpty()) { new MapToolEventBus().getMainEventBus().post(new AddOnsRemovedEvent(libs)); for (var library : namespaceLibraryMap.values()) { library.cleanup(); @@ -239,7 +238,7 @@ public void replaceLibrary(AddOnLibrary library) { * * @return the information of external add-on libraries that are registered. */ - public List getExternalAddOnLibraries() { + public List getExternalAddOnLibraries() { return externalAddOnLibraryManager.getLibraries(); } @@ -282,26 +281,19 @@ public void setExternalLibraryPath(Path path) { /** * Registers the add-on library as an external library. * - * @param addOnLibrary The add-on library to register. + * @param path The path to the library. */ - public void registerExternalLibrary(AddOnLibrary addOnLibrary) { - externalAddOnLibraryManager.registerExternalAddOnLibrary(addOnLibrary); + public void registerExternalLibrary(Path path) { + externalAddOnLibraryManager.registerExternalAddOnLibrary(path); } /** - * De-registers the add-on library with the given namespace. + * Makes the external library with the given namespace available to MapTool. Importing an existing + * library will replace the existing library. * * @param namespace The namespace of the library. */ - public void deregisterExternalLibrary(String namespace) { - externalAddOnLibraryManager.deregisterExternalAddOnLibrary(namespace); - } - - /** - * Makes the external library with the given namespace available to MapTool. - * @param namespace The namespace of the library. - */ - public void makeExternalLibraryAvailable(String namespace) { - externalAddOnLibraryManager.makeAvailable(namespace); + public void importFromExternal(String namespace) { + externalAddOnLibraryManager.importLibrary(namespace); } } diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java index d70c70989b..31b051eae5 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java @@ -14,6 +14,7 @@ */ package net.rptools.maptool.model.library.addon; +import com.google.common.eventbus.Subscribe; import io.methvin.watcher.DirectoryWatcher; import java.io.File; import java.io.IOException; @@ -23,13 +24,11 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; import net.rptools.maptool.events.MapToolEventBus; -import net.rptools.maptool.model.library.AddOnsRemovedEvent; -import net.rptools.maptool.model.library.LibraryInfo; /** * Manages the external add-on libraries that are baked by the file system. This manager will watch - * the external add-on library directory for changes and will update the add-on libraries - * available but will not automatically update the add-on libraries that MapTool has loaded. + * the external add-on library directory for changes and will update the add-on libraries available + * but will not automatically update the add-on libraries that MapTool has loaded. */ public class ExternalAddOnLibraryManager { @@ -37,13 +36,7 @@ public class ExternalAddOnLibraryManager { private final AddOnLibraryManager addOnLibraryManager; /** The add-on libraries that are registered. */ - private final Map namespaceLibraryMap = new ConcurrentHashMap<>(); - - /** Cache of the library info for the namespace. */ - private final Map namespaceLibraryInfoMap = new ConcurrentHashMap<>(); - - /** Cache of external library info. */ - private final List externalLibraryInfo; + private final Map namespaceInfoMap = new ConcurrentHashMap<>(); /** Is the external add-on library manager enabled. */ private boolean isEnabled = false; @@ -58,68 +51,140 @@ public class ExternalAddOnLibraryManager { private final ReentrantLock lock = new ReentrantLock(); /** - * Creates a new instance of the external add-on library manager. + * Creates a new instance of the external add-on library manager. {@code init()} must be called + * after construction. * * @param addOnLibraryManager the add-on library manager used to register the add-on libraries. */ public ExternalAddOnLibraryManager(AddOnLibraryManager addOnLibraryManager) { this.addOnLibraryManager = addOnLibraryManager; - externalLibraryInfo = Collections.synchronizedList(new ArrayList<>()); + } + + /** Initializes the external add-on library manager. */ + public void init() { + var eventBus = new MapToolEventBus().getMainEventBus(); + eventBus.register(this); } /** - * Registers an external add-on library. - * @param addOnLibrary the add-on library to register. + * Handles the event when an add-on library is added to MapTool. + * + * @param library the add-on library that was added to MapTool. */ - public void registerExternalAddOnLibrary(AddOnLibrary addOnLibrary) { - addOnLibrary + @Subscribe + public void onLibraryAdded(AddOnLibrary library) { + library .getNamespace() .thenAccept( namespace -> { - if (addOnLibraryManager.isNamespaceRegistered(namespace)) { - addOnLibraryManager.deregisterLibrary(namespace); // TODO: CDW This should only happen on import + if (namespaceInfoMap.containsKey(namespace)) { + var oldInfo = namespaceInfoMap.get(namespace); + // We do not want to use the library or the library info from the add-on library as + // disk may be different + var newInfo = + new ExternalLibraryInfo( + namespace, oldInfo.library(), oldInfo.libraryInfo(), false, true); + namespaceInfoMap.put(namespace, newInfo); } - namespaceLibraryMap.put(namespace, addOnLibrary); - addOnLibraryManager.registerLibrary(addOnLibrary); // TODO: CDW This should only happen on import - addOnLibrary - .getLibraryInfo() - .thenAccept( - libraryInfo -> { - namespaceLibraryInfoMap.put(namespace, libraryInfo); - }); - cacheExternalLibraryInfo(); }); } + /** + * Handles the event when an add-on library is removed from MapTool. + * + * @param library the add-on library that was removed from MapTool. + */ + @Subscribe + public void onLibraryRemoved(AddOnLibrary library) { + library + .getNamespace() + .thenAccept( + namespace -> { + if (namespaceInfoMap.containsKey(namespace)) { + var oldInfo = namespaceInfoMap.get(namespace); + // We do not want to use the library or the library info from the add-on library as + // disk may be different + var newInfo = + new ExternalLibraryInfo( + namespace, oldInfo.library(), oldInfo.libraryInfo(), true, false); + namespaceInfoMap.put(namespace, newInfo); + } + }); + } + + /** + * Handles the event when an add-on library is updated in MapTool. + * + * @param library the add-on library that was updated in MapTool. + */ + @Subscribe + public void onLibraryUpdated(AddOnLibrary library) { + library + .getNamespace() + .thenAccept( + namespace -> { + if (namespaceInfoMap.containsKey(namespace)) { + var oldInfo = namespaceInfoMap.get(namespace); + // We do not want to use the library or the library info from the add-on library as + // disk may be different + var newInfo = + new ExternalLibraryInfo( + namespace, oldInfo.library(), oldInfo.libraryInfo(), false, true); + namespaceInfoMap.put(namespace, newInfo); + } + }); + } + + /** + * Registers an external add-on library. + * + * @param addOnLibrary the add-on library to register. + */ + private void registerExternalAddOnLibrary(AddOnLibrary addOnLibrary) { + addOnLibrary + .getNamespace() + .thenAccept( + namespace -> + addOnLibrary + .getLibraryInfo() + .thenAccept( + libraryInfo -> { + boolean isInstalled = + addOnLibraryManager.isNamespaceRegistered(namespace); + var externalInfo = + new ExternalLibraryInfo( + namespace, addOnLibrary, libraryInfo, true, isInstalled); + namespaceInfoMap.put(namespace, externalInfo); + })); + } + /** * Deregisters an external add-on library. + * * @param namespace the namespace of the add-on library to deregister. */ public void deregisterExternalAddOnLibrary(String namespace) { - namespaceLibraryMap.remove(namespace.toLowerCase()); + namespaceInfoMap.remove(namespace.toLowerCase()); } /** - * Caches the external library information. + * Refreshes an external add-on library. + * + * @param path the path to the add-on library. */ - private void cacheExternalLibraryInfo() { - externalLibraryInfo.clear(); - var infoList = - namespaceLibraryInfoMap.values().stream() - .sorted((li1, li12) -> li1.name().compareToIgnoreCase(li12.name())) - .toList(); - externalLibraryInfo.addAll(infoList); + public void refreshExternalAddOnLibrary(Path path) { + registerExternalAddOnLibrary(path); // Allows us to change behaviour later without breaking API } /** - * Refreshes an external add-on library. - * @param namespace the namespace of the add-on library to refresh. + * Registers an external add-on library. + * * @param path the path to the add-on library. */ - public void refreshExternalAddOnLibrary(String namespace, Path path) { + public void registerExternalAddOnLibrary(Path path) { try { var lib = new AddOnLibraryImporter().importFromDirectory(path); - addOnLibraryManager.replaceLibrary(lib); // TODO: CDW This should only happen on import + registerExternalAddOnLibrary(lib); } catch (IOException e) { throw new RuntimeException(e); // TODO: CDW } @@ -127,14 +192,16 @@ public void refreshExternalAddOnLibrary(String namespace, Path path) { /** * Gets the external libraries that have been registered. + * * @return the external libraries. */ - public List getLibraries() { - return externalLibraryInfo; + public List getLibraries() { + return new ArrayList<>(namespaceInfoMap.values()); } /** * Is the external add-on library manager enabled. + * * @return {@code true} if the external add-on library manager is enabled. */ public boolean isEnabled() { @@ -148,6 +215,7 @@ public boolean isEnabled() { /** * Sets the enabled state of the external add-on library manager. + * * @param enabled the enabled state. */ public void setEnabled(boolean enabled) { @@ -168,6 +236,7 @@ public void setEnabled(boolean enabled) { /** * Gets the path to the external add-on libraries. + * * @return the path to the external add-on libraries. */ public Path getExternalLibraryPath() { @@ -181,6 +250,7 @@ public Path getExternalLibraryPath() { /** * Sets the path to the external add-on libraries. + * * @param path the path to the external add-on libraries. */ public void setExternalLibraryPath(Path path) { @@ -201,9 +271,7 @@ public void setExternalLibraryPath(Path path) { } } - /** - * Stops watching the external add-on library directory. - */ + /** Stops watching the external add-on library directory. */ private void stopWatching() { try { lock.lock(); @@ -220,9 +288,7 @@ private void stopWatching() { } } - /** - * Starts watching the external add-on library directory. - */ + /** Starts watching the external add-on library directory. */ private void startWatching() { try { lock.lock(); @@ -242,9 +308,7 @@ private void startWatching() { } } - /** - * Refreshes all the external add-on libraries. - */ + /** Refreshes all the external add-on libraries. */ private void refreshAll() { File[] directories = externalLibraryPath.toFile().listFiles(File::isDirectory); if (directories != null) { @@ -261,19 +325,21 @@ private void refreshAll() { /** * Makes the add-on library with the specified namespace available to MapTool. + * * @param namespace the namespace of the add-on library to make available. */ - public void makeAvailable(String namespace) { + public void importLibrary(String namespace) { if (isEnabled) { - var lib = namespaceLibraryMap.get(namespace); + var lib = namespaceInfoMap.get(namespace); if (lib != null) { - addOnLibraryManager.registerLibrary(lib); + addOnLibraryManager.registerLibrary(lib.library()); } } } /** * Stops the add-on library with the specified namespace from being available to MapTool. + * * @return the namespace of the add-on library to stop being available. * @throws IOException if an error occurs. */ @@ -285,12 +351,7 @@ private DirectoryWatcher createDirectoryWatcher() throws IOException { switch (event.eventType()) { case CREATE -> { if (event.path().relativize(externalLibraryPath).getNameCount() == 1) { - try { - var lib = new AddOnLibraryImporter().importFromDirectory(event.path()); - registerExternalAddOnLibrary(lib); - } catch (IOException e) { - throw new RuntimeException(e); // TODO: CDW - } + registerExternalAddOnLibrary(event.path()); } } case DELETE -> { @@ -300,8 +361,7 @@ private DirectoryWatcher createDirectoryWatcher() throws IOException { } case MODIFY -> { if (event.path().relativize(externalLibraryPath).getNameCount() == 1) { - refreshExternalAddOnLibrary( - event.path().getFileName().toString(), event.path()); + refreshExternalAddOnLibrary(event.path()); } } } @@ -310,4 +370,5 @@ private DirectoryWatcher createDirectoryWatcher() throws IOException { } } -// TODO: CDW changes to the add on library directory will not automatically update the add on library that MT uses \ No newline at end of file +// TODO: CDW changes to the add on library directory will not automatically update the add on +// library that MT uses diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalLibraryInfo.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalLibraryInfo.java new file mode 100644 index 0000000000..5edf9adf60 --- /dev/null +++ b/src/main/java/net/rptools/maptool/model/library/addon/ExternalLibraryInfo.java @@ -0,0 +1,33 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.model.library.addon; + +import net.rptools.maptool.model.library.LibraryInfo; + +/** + * Represents the information about external add-on library. + * + * @param namespace The namespace of the add-on. + * @param library The library created by the loading of the add-on from disk. + * @param libraryInfo The library info of the add-on. + * @param updatedOnDisk Whether the add-on has been updated on disk. + * @param isInstalled Whether the add-on is installed. + */ +public record ExternalLibraryInfo( + String namespace, + AddOnLibrary library, + LibraryInfo libraryInfo, + boolean updatedOnDisk, + boolean isInstalled) {} diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 7809cd125e..9000e0219c 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2925,6 +2925,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can''t be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.import.refresh = Refresh +library.dialog.import.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. From 45948015c60cc00b3d3ddf612c28aac09cb212b8 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Sat, 31 Aug 2024 11:40:36 +0930 Subject: [PATCH 010/146] fix for ui issues --- .../ui/addon/AddOnLibrariesDialogView.form | 206 +++++++++--------- .../ui/addon/AddOnLibrariesDialogView.java | 1 + .../addon/ExternalAddOnImportCellEditor.java | 12 +- .../ExternalAddOnLibrariesTableModel.java | 22 +- .../library/addon/AddOnLibraryManager.java | 4 + .../addon/ExternalAddOnLibraryManager.java | 7 +- .../rptools/maptool/language/i18n.properties | 6 +- 7 files changed, 138 insertions(+), 120 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form index e6af53d67f..1b124ebd47 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form @@ -1,31 +1,26 @@
- - + - + + - - - - - - + - - - + + + @@ -41,8 +36,8 @@ - - + + @@ -55,15 +50,19 @@ - + - + - + + + + + @@ -188,6 +187,7 @@ + @@ -283,84 +283,126 @@ - - + - - - - - - - - - + - + + - + - + - + + - + - + + - + - + - + + - + - + - + + - + + + - - + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + @@ -368,59 +410,17 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - + - - - - - - - - @@ -429,9 +429,7 @@ - - - + diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index e7adad806f..e585cc6198 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -246,6 +246,7 @@ public void windowClosing(WindowEvent e) { if (enableExternalAddOnCheckBox.isSelected()) { refreshLibraries(); } + pack(); } private void refreshLibraries() {} diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java index b0256e6757..599b46b3fe 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java @@ -18,6 +18,8 @@ import javax.swing.*; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; +import net.rptools.maptool.language.I18N; +import net.rptools.maptool.model.library.LibraryManager; import net.rptools.maptool.model.library.addon.ExternalLibraryInfo; public class ExternalAddOnImportCellEditor extends AbstractCellEditor @@ -29,7 +31,7 @@ public class ExternalAddOnImportCellEditor extends AbstractCellEditor public ExternalAddOnImportCellEditor() { button.addActionListener( e -> { - System.out.println("Button clicked: " + info.namespace()); + new LibraryManager().importFromExternal(info.libraryInfo().namespace()); stopCellEditing(); }); } @@ -55,7 +57,13 @@ private Component getButton( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { button.setEnabled(true); info = (ExternalLibraryInfo) value; - button.setText(info.namespace()); + String buttonTextKey; + if (info.isInstalled()) { + buttonTextKey = "library.dialog.reimport"; + } else { + buttonTextKey = "library.dialog.import"; + } + button.setText(I18N.getText(buttonTextKey)); return button; } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java index c63247a195..71a2b7658f 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java @@ -29,17 +29,21 @@ public int getRowCount() { @Override public int getColumnCount() { - return 5; + return 7; } @Override public Class getColumnClass(int columnIndex) { - return columnIndex == 4 ? ExternalLibraryInfo.class : String.class; + return switch (columnIndex) { + case 4, 5 -> Boolean.class; + case 6 -> ExternalLibraryInfo.class; + default -> String.class; + }; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { - return columnIndex == 4; + return getColumnClass(columnIndex) == ExternalLibraryInfo.class; } @Override @@ -52,7 +56,9 @@ public Object getValueAt(int rowIndex, int columnIndex) { case 1 -> info.libraryInfo().version(); case 2 -> info.libraryInfo().namespace(); case 3 -> info.libraryInfo().backingDirectory(); - case 4 -> info; + case 4 -> info.isInstalled(); + case 5 -> info.updatedOnDisk(); + case 6 -> info; default -> null; }; } @@ -64,12 +70,10 @@ public String getColumnName(int column) { case 1 -> I18N.getText("library.dialog.addon.version"); case 2 -> I18N.getText("library.dialog.addon.namespace"); case 3 -> I18N.getText("library.dialog.addon.subdir"); - case 4 -> I18N.getText("library.dialog.addon.refresh"); + case 4 -> I18N.getText("library.dialog.addon.imported"); + case 5 -> I18N.getText("library.dialog.addon.updated"); + case 6 -> I18N.getText("library.dialog.addon.refresh"); default -> null; }; } - - void refresh(int rowIndex) { - // TODO: CDW - } } diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java index 93ee7559fd..70cfa7d804 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java @@ -211,6 +211,10 @@ public CompletableFuture> getLegacyEventTargets(String eventName) { /** Initializes the add-on library manager. */ public void init() { externalAddOnLibraryManager = new ExternalAddOnLibraryManager(this); + externalAddOnLibraryManager.init(); + + + String path = AppPreferences.getExternalAddOnLibrariesPath(); if (path != null && !path.isEmpty()) { externalAddOnLibraryManager.setExternalLibraryPath(Path.of(path)); diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java index 31b051eae5..85d71ea572 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java @@ -261,9 +261,7 @@ public void setExternalLibraryPath(Path path) { lock.lock(); externalLibraryPath = path; stopWatching(); - if (path == null) { - isEnabled = false; - } else { + if (path != null) { startWatching(); } } finally { @@ -310,6 +308,9 @@ private void startWatching() { /** Refreshes all the external add-on libraries. */ private void refreshAll() { + if (externalLibraryPath == null) { + return; + } File[] directories = externalLibraryPath.toFile().listFiles(File::isDirectory); if (directories != null) { for (File directory : directories) { diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 9000e0219c..7abaa3ccf7 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2925,8 +2925,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can''t be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.import.refresh = Refresh -library.dialog.import.import = Import +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2982,6 +2982,8 @@ library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard library.dialog.externalAddon = Enable external add-on libraries (development purposes only) library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated library.dialog.addon.refresh = Refresh library.dialog.addon.createNew = Create New Add-On library.dialog.addon.export = Export Add-On From c736ee9d1fd0075147fa66a3be65400a0fe7f906 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Sat, 31 Aug 2024 21:39:31 +0930 Subject: [PATCH 011/146] more work on the file change logic --- .../ui/addon/AddOnLibrariesDialogView.java | 27 +- .../ui/addon/AddOnLibrariesTableModel.java | 22 ++ .../addon/ExternalAddOnImportCellEditor.java | 15 +- .../ExternalAddOnLibrariesTableModel.java | 14 +- .../library/ExternalAddonsUpdateEvent.java | 18 ++ .../maptool/model/library/LibraryInfo.java | 5 +- .../maptool/model/library/LibraryManager.java | 18 +- .../model/library/addon/AddOnLibrary.java | 3 +- .../library/addon/AddOnLibraryImporter.java | 37 ++- .../library/addon/AddOnLibraryManager.java | 38 ++- .../addon/ExternalAddOnLibraryManager.java | 255 +++++++++--------- .../library/addon/ExternalLibraryInfo.java | 9 +- .../builtin/MapToolBuiltInLibrary.java | 3 +- .../model/library/token/LibraryToken.java | 1 - 14 files changed, 303 insertions(+), 162 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/model/library/ExternalAddonsUpdateEvent.java diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index e585cc6198..3179de74d0 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -42,6 +42,7 @@ import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.ui.JLabelHyperLinkListener; import net.rptools.maptool.client.ui.ViewAssetDialog; +import net.rptools.maptool.events.MapToolEventBus; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.library.Library; import net.rptools.maptool.model.library.LibraryInfo; @@ -80,15 +81,27 @@ public class AddOnLibrariesDialogView extends JDialog { private LibraryInfo selectedAddOn; + private final ExternalAddOnLibrariesTableModel externalAddOnLibrariesTableModel; + private final AddOnLibrariesTableModel addOnLibrariesTableModel; + /** Creates a new instance of the dialog. */ public AddOnLibrariesDialogView() { setContentPane(contentPane); setModal(true); getRootPane().setDefaultButton(buttonClose); - addOnLibraryTable.setModel(new AddOnLibrariesTableModel()); + + var eventBus = new MapToolEventBus().getMainEventBus(); + + addOnLibrariesTableModel = new AddOnLibrariesTableModel(); + eventBus.register(addOnLibrariesTableModel); + + addOnLibraryTable.setModel(addOnLibrariesTableModel); addOnLibraryTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - externalAddonTable.setModel(new ExternalAddOnLibrariesTableModel()); + externalAddOnLibrariesTableModel = new ExternalAddOnLibrariesTableModel(); + externalAddonTable.setModel(externalAddOnLibrariesTableModel); + eventBus.register(externalAddOnLibrariesTableModel); + externalAddonTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); externalAddonTable.setDefaultRenderer( ExternalLibraryInfo.class, new ExternalAddOnImportCellEditor()); @@ -307,6 +320,9 @@ private void selectedAddOnChanged(LibraryInfo addOn) { /** Closes the dialog. */ private void onClose() { dispose(); + var eventBus = new MapToolEventBus().getMainEventBus(); + eventBus.unregister(externalAddOnLibrariesTableModel); + eventBus.unregister(addOnLibrariesTableModel); } /** Add an add-on library to the library manager. */ @@ -359,11 +375,4 @@ private void viewReadMeFile(LibraryInfo libInfo) { a.ifPresent( asset -> new ViewAssetDialog(asset, "License", 640, 480).showModal()))); } - - public static void main(String[] args) { - AddOnLibrariesDialogView dialog = new AddOnLibrariesDialogView(); - dialog.pack(); - dialog.setVisible(true); - System.exit(0); - } } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java index cdc5e83822..4805fc8cd0 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java @@ -14,11 +14,13 @@ */ package net.rptools.maptool.client.ui.addon; +import com.google.common.eventbus.Subscribe; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import javax.swing.table.AbstractTableModel; import net.rptools.maptool.language.I18N; +import net.rptools.maptool.model.library.AddOnsAddedEvent; import net.rptools.maptool.model.library.LibraryInfo; import net.rptools.maptool.model.library.LibraryManager; import net.rptools.maptool.model.library.LibraryType; @@ -85,4 +87,24 @@ public void fireTableDataChanged() { } super.fireTableDataChanged(); } + + /** + * Handle the AddOnsUpdatedEvent event by firing a table data changed event. + * + * @param event the AddOnsUpdatedEvent event. + */ + @Subscribe + public void handleAddOnsUpdatedEvent(AddOnsAddedEvent event) { + fireTableDataChanged(); + } + + /** + * Handle the AddOnsRemovedEvent event by firing a table data changed event. + * + * @param event the AddOnsRemovedEvent event. + */ + @Subscribe + public void handleAddOnsRemovedEvent(AddOnsAddedEvent event) { + fireTableDataChanged(); + } } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java index 599b46b3fe..16f65c898d 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java @@ -15,7 +15,9 @@ package net.rptools.maptool.client.ui.addon; import java.awt.*; +import java.io.IOException; import javax.swing.*; +import javax.swing.border.EmptyBorder; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import net.rptools.maptool.language.I18N; @@ -31,7 +33,11 @@ public class ExternalAddOnImportCellEditor extends AbstractCellEditor public ExternalAddOnImportCellEditor() { button.addActionListener( e -> { - new LibraryManager().importFromExternal(info.libraryInfo().namespace()); + try { + new LibraryManager().importFromExternal(info.libraryInfo().namespace()); + } catch (IOException ex) { + throw new RuntimeException(ex); // TODO: CDW + } stopCellEditing(); }); } @@ -65,6 +71,11 @@ private Component getButton( } button.setText(I18N.getText(buttonTextKey)); - return button; + var panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.add(button, BorderLayout.CENTER); + panel.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); + panel.setBorder(new EmptyBorder(1, 5, 2, 5)); + return panel; } } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java index 71a2b7658f..a0417721a5 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java @@ -14,9 +14,11 @@ */ package net.rptools.maptool.client.ui.addon; +import com.google.common.eventbus.Subscribe; import java.util.List; import javax.swing.table.AbstractTableModel; import net.rptools.maptool.language.I18N; +import net.rptools.maptool.model.library.ExternalAddonsUpdateEvent; import net.rptools.maptool.model.library.LibraryManager; import net.rptools.maptool.model.library.addon.ExternalLibraryInfo; @@ -55,7 +57,7 @@ public Object getValueAt(int rowIndex, int columnIndex) { case 0 -> info.libraryInfo().name(); case 1 -> info.libraryInfo().version(); case 2 -> info.libraryInfo().namespace(); - case 3 -> info.libraryInfo().backingDirectory(); + case 3 -> info.subDirectoryName(); case 4 -> info.isInstalled(); case 5 -> info.updatedOnDisk(); case 6 -> info; @@ -76,4 +78,14 @@ public String getColumnName(int column) { default -> null; }; } + + /** + * Handle the event when external addons are added. + * + * @param event the AddOnsAddedEvent event. + */ + @Subscribe + public void handleExternalAddOnsAdded(ExternalAddonsUpdateEvent event) { + fireTableDataChanged(); + } } diff --git a/src/main/java/net/rptools/maptool/model/library/ExternalAddonsUpdateEvent.java b/src/main/java/net/rptools/maptool/model/library/ExternalAddonsUpdateEvent.java new file mode 100644 index 0000000000..f0cdfcbbfb --- /dev/null +++ b/src/main/java/net/rptools/maptool/model/library/ExternalAddonsUpdateEvent.java @@ -0,0 +1,18 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.model.library; + +/** Event that is fired when the list of external addons is updated. */ +public record ExternalAddonsUpdateEvent() {} diff --git a/src/main/java/net/rptools/maptool/model/library/LibraryInfo.java b/src/main/java/net/rptools/maptool/model/library/LibraryInfo.java index 50bf969abf..68a239a149 100644 --- a/src/main/java/net/rptools/maptool/model/library/LibraryInfo.java +++ b/src/main/java/net/rptools/maptool/model/library/LibraryInfo.java @@ -14,8 +14,6 @@ */ package net.rptools.maptool.model.library; -import java.nio.file.Path; - /** Record that contains the information about a library. */ public record LibraryInfo( String name, @@ -29,6 +27,5 @@ public record LibraryInfo( String shortDescription, boolean allowsUrlAccess, String readMeFile, - String licenseFile, - Path backingDirectory) {} + String licenseFile) {} ; diff --git a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java index 6bc55be813..bd8d589ec0 100644 --- a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java @@ -14,6 +14,7 @@ */ package net.rptools.maptool.model.library; +import java.io.IOException; import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; @@ -71,6 +72,10 @@ public class LibraryManager { private static final AddOnSlashCommandManager addOnSlashCommandManager = new AddOnSlashCommandManager(); + /** + * Initializes the library manager. This method should be called after + * instantiation of the library manager. + */ public static void init() { libraryTokenManager.init(); builtInLibraryManager.loadBuiltIns(); @@ -118,8 +123,10 @@ public Path getEternalLibraryPath() { * Sets the path to the external add-on libraries. * * @param path the path to the external add-on libraries. + * + * @throws IOException if an error occurs while setting the path. */ - public void setExternalLibraryPath(Path path) { + public void setExternalLibraryPath(Path path) throws IOException { addOnLibraryManager.setExternalLibraryPath(path); } @@ -257,8 +264,10 @@ public boolean reregisterAddOnLibrary(AddOnLibrary addOnLibrary) { * MapTool. To make the add-on available to MapTool, use {@link #importFromExternal(String)} * * @param path The path of the add-on to register. + * + * @throws IOException if an error occurs while registering the add-on. */ - public void registerExternalAddOnLibrary(Path path) { + public void registerExternalAddOnLibrary(Path path) throws IOException { addOnLibraryManager.registerExternalLibrary(path); } @@ -268,7 +277,10 @@ public void registerExternalAddOnLibrary(Path path) { * * @param namespace The namespace of the add-on to import. */ - public void importFromExternal(String namespace) { + public void importFromExternal(String namespace) throws IOException { + if (addOnLibraryManager.namespaceRegistered(namespace)) { + addOnLibraryManager.deregisterLibrary(namespace); + } addOnLibraryManager.importFromExternal(namespace); } diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java index f7f62d404d..d3994cca7a 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java @@ -283,8 +283,7 @@ private AddOnLibrary( shortDescription, allowsUriAccess, readMeFile.isEmpty() ? null : readMeFile, - licenseFile.isEmpty() ? null : licenseFile, - backingDirectory); + licenseFile.isEmpty() ? null : licenseFile); for (var s : slashCommandsDto.getSlashCommandsList()) { slashCommands.put( diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java index 20237a3788..1eb0d30626 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java @@ -35,6 +35,7 @@ import net.rptools.maptool.model.Asset; import net.rptools.maptool.model.Asset.Type; import net.rptools.maptool.model.AssetManager; +import net.rptools.maptool.model.library.LibraryInfo; import net.rptools.maptool.model.library.proto.AddOnLibraryDto; import net.rptools.maptool.model.library.proto.AddOnLibraryEventsDto; import net.rptools.maptool.model.library.proto.AddOnStatSheetsDto; @@ -140,6 +141,39 @@ public AddOnLibrary importFromAsset(Asset asset) throws IOException { return importFromFile(tempFile); } + /** + * Imports the {@link LibraryInfo} from the specified directory. If the {@code LIBRARY_INFO_FILE} + * does not exist in this directory then null will be returned. + * + * @param dir The directory to import the library info from. + * @return the {@link LibraryInfo} that was imported, {@code null} if the library info file does + * not exist. + * @throws IOException if an error occurs while reading the library. + */ + public LibraryInfo getLibraryInfoFromDirectory(Path dir) throws IOException { + var infoPath = dir.resolve(LIBRARY_INFO_FILE).toAbsolutePath(); + if (!Files.exists(infoPath)) { + return null; + } + + var builder = AddOnLibraryDto.newBuilder(); + JsonFormat.parser().ignoringUnknownFields().merge(Files.newBufferedReader(infoPath), builder); + + return new LibraryInfo( + builder.getName(), + builder.getNamespace(), + builder.getVersion(), + builder.getWebsite(), + builder.getGitUrl(), + builder.getAuthorsList().toArray(new String[0]), + builder.getLicense(), + builder.getDescription(), + builder.getShortDescription(), + builder.getAllowsUriAccess(), + builder.getReadMeFile(), + builder.getLicenseFile()); + } + /** * Imports the add-on library from the specified directory * @@ -150,8 +184,9 @@ public AddOnLibrary importFromAsset(Asset asset) throws IOException { public AddOnLibrary importFromDirectory(Path dir) throws IOException { var infoPath = dir.resolve(LIBRARY_INFO_FILE); - if (!Files.exists(infoPath)) + if (!Files.exists(infoPath)) { throw new IOException(I18N.getText("library.error.addOn.noConfigFile", dir)); + } var builder = AddOnLibraryDto.newBuilder(); JsonFormat.parser().ignoringUnknownFields().merge(Files.newBufferedReader(infoPath), builder); diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java index 70cfa7d804..9374f25e4c 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java @@ -14,6 +14,7 @@ */ package net.rptools.maptool.model.library.addon; +import java.io.IOException; import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; @@ -25,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import net.rptools.maptool.client.AppPreferences; +import net.rptools.maptool.client.MapTool; import net.rptools.maptool.events.MapToolEventBus; import net.rptools.maptool.model.library.AddOnsAddedEvent; import net.rptools.maptool.model.library.AddOnsRemovedEvent; @@ -42,6 +44,7 @@ public class AddOnLibraryManager { /** The add-on libraries that are registered. */ private final Map namespaceLibraryMap = new ConcurrentHashMap<>(); + /** The external add-on library manager. */ private ExternalAddOnLibraryManager externalAddOnLibraryManager; /** @@ -208,17 +211,28 @@ public CompletableFuture> getLegacyEventTargets(String eventName) { .collect(Collectors.toSet())); } - /** Initializes the add-on library manager. */ + /** + * Initializes the add-on library manager. + * + */ public void init() { externalAddOnLibraryManager = new ExternalAddOnLibraryManager(this); externalAddOnLibraryManager.init(); - - String path = AppPreferences.getExternalAddOnLibrariesPath(); if (path != null && !path.isEmpty()) { - externalAddOnLibraryManager.setExternalLibraryPath(Path.of(path)); - externalAddOnLibraryManager.setEnabled(AppPreferences.getExternalLibraryManagerEnabled()); + try { + externalAddOnLibraryManager.setExternalLibraryPath(Path.of(path)); + externalAddOnLibraryManager.setEnabled(AppPreferences.getExternalLibraryManagerEnabled()); + } catch (IOException e) { + MapTool.showError("Error setting external library path", e); + try { + externalAddOnLibraryManager.setExternalLibraryPath(null); + externalAddOnLibraryManager.setEnabled(false); + } catch (IOException ex) { + // Do nothing. + } + } } } @@ -259,8 +273,10 @@ public boolean externalLibrariesEnabled() { * Sets if external add-on libraries are enabled. * * @param enabled if external add-on libraries are enabled. + * + * @throws IOException if an I/O error occurs. */ - public void setExternalLibrariesEnabled(boolean enabled) { + public void setExternalLibrariesEnabled(boolean enabled) throws IOException { externalAddOnLibraryManager.setEnabled(enabled); } @@ -277,8 +293,10 @@ public Path getExternalLibraryPath() { * Sets the path to the external add-on libraries. * * @param path the path to the external add-on libraries. + * + * @throws IOException if an I/O error occurs. */ - public void setExternalLibraryPath(Path path) { + public void setExternalLibraryPath(Path path) throws IOException { externalAddOnLibraryManager.setExternalLibraryPath(path); } @@ -286,8 +304,10 @@ public void setExternalLibraryPath(Path path) { * Registers the add-on library as an external library. * * @param path The path to the library. + * + * @throws IOException if an I/O error occurs. */ - public void registerExternalLibrary(Path path) { + public void registerExternalLibrary(Path path) throws IOException { externalAddOnLibraryManager.registerExternalAddOnLibrary(path); } @@ -297,7 +317,7 @@ public void registerExternalLibrary(Path path) { * * @param namespace The namespace of the library. */ - public void importFromExternal(String namespace) { + public void importFromExternal(String namespace) throws IOException { externalAddOnLibraryManager.importLibrary(namespace); } } diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java index 85d71ea572..34be238ba9 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java @@ -24,6 +24,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; import net.rptools.maptool.events.MapToolEventBus; +import net.rptools.maptool.model.library.AddOnsAddedEvent; +import net.rptools.maptool.model.library.AddOnsRemovedEvent; +import net.rptools.maptool.model.library.ExternalAddonsUpdateEvent; +import net.rptools.maptool.model.library.LibraryInfo; /** * Manages the external add-on libraries that are baked by the file system. This manager will watch @@ -69,110 +73,100 @@ public void init() { /** * Handles the event when an add-on library is added to MapTool. * - * @param library the add-on library that was added to MapTool. + * @param event the add-on library that was added to MapTool. */ @Subscribe - public void onLibraryAdded(AddOnLibrary library) { - library - .getNamespace() - .thenAccept( - namespace -> { - if (namespaceInfoMap.containsKey(namespace)) { - var oldInfo = namespaceInfoMap.get(namespace); - // We do not want to use the library or the library info from the add-on library as - // disk may be different - var newInfo = - new ExternalLibraryInfo( - namespace, oldInfo.library(), oldInfo.libraryInfo(), false, true); - namespaceInfoMap.put(namespace, newInfo); - } - }); + public void onLibraryAdded(AddOnsAddedEvent event) { + boolean updated = false; + for (LibraryInfo libraryInfo : event.addOns()) { + var namespace = libraryInfo.namespace().toLowerCase(); + if (namespaceInfoMap.containsKey(namespace)) { + var oldInfo = namespaceInfoMap.get(namespace); + var newInfo = + new ExternalLibraryInfo( + namespace, + oldInfo.libraryInfo(), + false, + true, + oldInfo.backingDirectory(), + externalLibraryPath.relativize(oldInfo.backingDirectory()).toString()); + namespaceInfoMap.put(namespace, newInfo); + updated = true; + } + } + if (updated) { + new MapToolEventBus().getMainEventBus().post(new ExternalAddonsUpdateEvent()); + } } /** * Handles the event when an add-on library is removed from MapTool. * - * @param library the add-on library that was removed from MapTool. - */ - @Subscribe - public void onLibraryRemoved(AddOnLibrary library) { - library - .getNamespace() - .thenAccept( - namespace -> { - if (namespaceInfoMap.containsKey(namespace)) { - var oldInfo = namespaceInfoMap.get(namespace); - // We do not want to use the library or the library info from the add-on library as - // disk may be different - var newInfo = - new ExternalLibraryInfo( - namespace, oldInfo.library(), oldInfo.libraryInfo(), true, false); - namespaceInfoMap.put(namespace, newInfo); - } - }); - } - - /** - * Handles the event when an add-on library is updated in MapTool. - * - * @param library the add-on library that was updated in MapTool. + * @param event the add-on library that was removed from MapTool. */ @Subscribe - public void onLibraryUpdated(AddOnLibrary library) { - library - .getNamespace() - .thenAccept( - namespace -> { - if (namespaceInfoMap.containsKey(namespace)) { - var oldInfo = namespaceInfoMap.get(namespace); - // We do not want to use the library or the library info from the add-on library as - // disk may be different - var newInfo = - new ExternalLibraryInfo( - namespace, oldInfo.library(), oldInfo.libraryInfo(), false, true); - namespaceInfoMap.put(namespace, newInfo); - } - }); + public void onLibraryRemoved(AddOnsRemovedEvent event) { + boolean updated = false; + for (LibraryInfo libraryInfo : event.addOns()) { + var namespace = libraryInfo.namespace().toLowerCase(); + if (namespaceInfoMap.containsKey(namespace)) { + var oldInfo = namespaceInfoMap.get(namespace); + var newInfo = + new ExternalLibraryInfo( + namespace, + oldInfo.libraryInfo(), + true, + false, + oldInfo.backingDirectory(), + externalLibraryPath.relativize(oldInfo.backingDirectory()).toString()); + namespaceInfoMap.put(namespace, newInfo); + updated = true; + } + } + if (updated) { + new MapToolEventBus().getMainEventBus().post(new ExternalAddonsUpdateEvent()); + } } /** * Registers an external add-on library. * - * @param addOnLibrary the add-on library to register. + * @param info Information about the add-on library to register. */ - private void registerExternalAddOnLibrary(AddOnLibrary addOnLibrary) { - addOnLibrary - .getNamespace() - .thenAccept( - namespace -> - addOnLibrary - .getLibraryInfo() - .thenAccept( - libraryInfo -> { - boolean isInstalled = - addOnLibraryManager.isNamespaceRegistered(namespace); - var externalInfo = - new ExternalLibraryInfo( - namespace, addOnLibrary, libraryInfo, true, isInstalled); - namespaceInfoMap.put(namespace, externalInfo); - })); + private void registerExternalAddOnLibrary(ExternalLibraryInfo info) { + boolean isInstalled = addOnLibraryManager.isNamespaceRegistered(info.namespace()); + var externalInfo = + new ExternalLibraryInfo( + info.namespace(), + info.libraryInfo(), + true, + isInstalled, + info.backingDirectory(), + externalLibraryPath.relativize(info.backingDirectory()).toString()); + namespaceInfoMap.put(info.namespace().toLowerCase(), externalInfo); } /** * Deregisters an external add-on library. * - * @param namespace the namespace of the add-on library to deregister. + * @param path the backing path of the add-on library to deregister. */ - public void deregisterExternalAddOnLibrary(String namespace) { - namespaceInfoMap.remove(namespace.toLowerCase()); + public void deregisterExternalAddOnLibrary(Path path) { + namespaceInfoMap.values().stream() + .filter(info -> info.backingDirectory().equals(path)) + .findFirst() + .ifPresent(info -> namespaceInfoMap.remove(info.namespace().toLowerCase())); + var eventBus = new MapToolEventBus().getMainEventBus(); + eventBus.post(new ExternalAddonsUpdateEvent()); } /** * Refreshes an external add-on library. * * @param path the path to the add-on library. + * @throws IOException if an error occurs. */ - public void refreshExternalAddOnLibrary(Path path) { + public void refreshExternalAddOnLibrary(Path path) throws IOException { registerExternalAddOnLibrary(path); // Allows us to change behaviour later without breaking API } @@ -180,14 +174,25 @@ public void refreshExternalAddOnLibrary(Path path) { * Registers an external add-on library. * * @param path the path to the add-on library. + * + * @throws IOException if an error occurs. */ - public void registerExternalAddOnLibrary(Path path) { - try { - var lib = new AddOnLibraryImporter().importFromDirectory(path); - registerExternalAddOnLibrary(lib); - } catch (IOException e) { - throw new RuntimeException(e); // TODO: CDW - } + public void registerExternalAddOnLibrary(Path path) throws IOException { + var lib = new AddOnLibraryImporter().getLibraryInfoFromDirectory(path); + if (lib == null) { + return; + } + var info = + new ExternalLibraryInfo( + lib.namespace(), + lib, + false, + false, + path, + externalLibraryPath.relativize(path).toString()); + registerExternalAddOnLibrary(info); + var eventBus = new MapToolEventBus().getMainEventBus(); + eventBus.post(new ExternalAddonsUpdateEvent()); } /** @@ -217,8 +222,10 @@ public boolean isEnabled() { * Sets the enabled state of the external add-on library manager. * * @param enabled the enabled state. + * + * @throws IOException if an error occurs. */ - public void setEnabled(boolean enabled) { + public void setEnabled(boolean enabled) throws IOException { try { lock.lock(); if (isEnabled != enabled) { @@ -252,8 +259,10 @@ public Path getExternalLibraryPath() { * Sets the path to the external add-on libraries. * * @param path the path to the external add-on libraries. + * + * @throws IOException if an error occurs. */ - public void setExternalLibraryPath(Path path) { + public void setExternalLibraryPath(Path path) throws IOException { if (path != null && path.equals(externalLibraryPath)) { return; } @@ -270,24 +279,24 @@ public void setExternalLibraryPath(Path path) { } /** Stops watching the external add-on library directory. */ - private void stopWatching() { + private void stopWatching() throws IOException { try { lock.lock(); if (directoryWatcher != null) { - try { - directoryWatcher.close(); - directoryWatcher = null; - } catch (IOException e) { - throw new RuntimeException(e); // TODO: CDW - } + directoryWatcher.close(); + directoryWatcher = null; } } finally { lock.unlock(); } } - /** Starts watching the external add-on library directory. */ - private void startWatching() { + /** + * Starts watching the external add-on library directory. + * + * @throws IOException if an error occurs. + */ + private void startWatching() throws IOException { try { lock.lock(); refreshAll(); @@ -299,29 +308,28 @@ private void startWatching() { directoryWatcher.watchAsync(); } } - } catch (IOException e) { - throw new RuntimeException(e); // TODO: CDW } finally { lock.unlock(); } } - /** Refreshes all the external add-on libraries. */ - private void refreshAll() { + /** + * Refreshes all the external add-on libraries. + * + * @throws IOException if an error occurs. + */ + private void refreshAll() throws IOException { if (externalLibraryPath == null) { return; } File[] directories = externalLibraryPath.toFile().listFiles(File::isDirectory); if (directories != null) { for (File directory : directories) { - try { - var lib = new AddOnLibraryImporter().importFromDirectory(directory.toPath()); - registerExternalAddOnLibrary(lib); - } catch (IOException e) { - throw new RuntimeException(e); // TODO: CDW - } + registerExternalAddOnLibrary(directory.toPath()); } } + var eventBus = new MapToolEventBus().getMainEventBus(); + eventBus.post(new ExternalAddonsUpdateEvent()); } /** @@ -329,11 +337,12 @@ private void refreshAll() { * * @param namespace the namespace of the add-on library to make available. */ - public void importLibrary(String namespace) { + public void importLibrary(String namespace) throws IOException { if (isEnabled) { - var lib = namespaceInfoMap.get(namespace); - if (lib != null) { - addOnLibraryManager.registerLibrary(lib.library()); + var libInfo = namespaceInfoMap.get(namespace.toLowerCase()); + if (libInfo != null) { + var lib = new AddOnLibraryImporter().importFromDirectory(libInfo.backingDirectory()); + addOnLibraryManager.registerLibrary(lib); } } } @@ -349,27 +358,23 @@ private DirectoryWatcher createDirectoryWatcher() throws IOException { .path(externalLibraryPath) .listener( event -> { + int basePathNameCount = externalLibraryPath.getNameCount(); + var path = event.path(); + if (path.getNameCount() <= basePathNameCount) { + return; + } + if (!path.startsWith(externalLibraryPath)) { + return; + } + path = externalLibraryPath.resolve(path.getName(basePathNameCount)); switch (event.eventType()) { - case CREATE -> { - if (event.path().relativize(externalLibraryPath).getNameCount() == 1) { - registerExternalAddOnLibrary(event.path()); - } - } - case DELETE -> { - if (event.path().relativize(externalLibraryPath).getNameCount() == 1) { - deregisterExternalAddOnLibrary(event.path().getFileName().toString()); - } - } - case MODIFY -> { - if (event.path().relativize(externalLibraryPath).getNameCount() == 1) { - refreshExternalAddOnLibrary(event.path()); - } - } + case CREATE -> registerExternalAddOnLibrary(path); + case DELETE -> deregisterExternalAddOnLibrary(path); + case MODIFY -> refreshExternalAddOnLibrary(path); } }) .build(); } } -// TODO: CDW changes to the add on library directory will not automatically update the add on -// library that MT uses +// TODO: CDW dont crash on invalid library file diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalLibraryInfo.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalLibraryInfo.java index 5edf9adf60..eed3446292 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/ExternalLibraryInfo.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/ExternalLibraryInfo.java @@ -14,20 +14,23 @@ */ package net.rptools.maptool.model.library.addon; +import java.nio.file.Path; import net.rptools.maptool.model.library.LibraryInfo; /** * Represents the information about external add-on library. * * @param namespace The namespace of the add-on. - * @param library The library created by the loading of the add-on from disk. * @param libraryInfo The library info of the add-on. * @param updatedOnDisk Whether the add-on has been updated on disk. * @param isInstalled Whether the add-on is installed. + * @param backingDirectory The backing directory of the add-on. + * @param subDirectoryName The subdirectory name of the add-on in the add-on development dir. */ public record ExternalLibraryInfo( String namespace, - AddOnLibrary library, LibraryInfo libraryInfo, boolean updatedOnDisk, - boolean isInstalled) {} + boolean isInstalled, + Path backingDirectory, + String subDirectoryName) {} diff --git a/src/main/java/net/rptools/maptool/model/library/builtin/MapToolBuiltInLibrary.java b/src/main/java/net/rptools/maptool/model/library/builtin/MapToolBuiltInLibrary.java index 1ec104ebd7..f9c6e5d987 100644 --- a/src/main/java/net/rptools/maptool/model/library/builtin/MapToolBuiltInLibrary.java +++ b/src/main/java/net/rptools/maptool/model/library/builtin/MapToolBuiltInLibrary.java @@ -222,8 +222,7 @@ public CompletableFuture getLibraryInfo() { shortDescription, allowsUriAccess, readMeFile.isEmpty() ? null : readMeFile, - licenseFile.isEmpty() ? null : licenseFile, - null)); + licenseFile.isEmpty() ? null : licenseFile)); } @Override diff --git a/src/main/java/net/rptools/maptool/model/library/token/LibraryToken.java b/src/main/java/net/rptools/maptool/model/library/token/LibraryToken.java index 88989907bc..b6bf0504a5 100644 --- a/src/main/java/net/rptools/maptool/model/library/token/LibraryToken.java +++ b/src/main/java/net/rptools/maptool/model/library/token/LibraryToken.java @@ -297,7 +297,6 @@ public CompletableFuture getLibraryInfo() { shortDescription.isEmpty() ? notSet : shortDescription, allowsUriAccess, null, - null, null)); } From ea44cd858bb21b74399cc6d4747366b124561724 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Mon, 2 Sep 2024 18:38:11 +0930 Subject: [PATCH 012/146] Start Add On Skeleton creation --- .../maptool/client/AppPreferences.java | 21 ++ .../ui/addon/AddOnLibrariesDialogView.form | 4 +- .../ui/addon/AddOnLibrariesDialogView.java | 22 +- .../client/ui/addon/CreateNewAddonDialog.form | 327 ++++++++++++++++++ .../client/ui/addon/CreateNewAddonDialog.java | 203 +++++++++++ .../addon/ExternalAddOnImportCellEditor.java | 4 +- .../client/ui/addon/creator/NewAddOn.java | 34 ++ .../ui/addon/creator/NewAddOnBuilder.java | 131 +++++++ .../maptool/model/library/LibraryManager.java | 8 +- .../library/addon/AddOnLibraryManager.java | 31 +- .../addon/ExternalAddOnLibraryManager.java | 121 ++++--- .../rptools/maptool/language/i18n.properties | 20 +- 12 files changed, 840 insertions(+), 86 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.form create mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java create mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java create mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java diff --git a/src/main/java/net/rptools/maptool/client/AppPreferences.java b/src/main/java/net/rptools/maptool/client/AppPreferences.java index 7be9d27e12..5a13273c06 100644 --- a/src/main/java/net/rptools/maptool/client/AppPreferences.java +++ b/src/main/java/net/rptools/maptool/client/AppPreferences.java @@ -153,6 +153,9 @@ public class AppPreferences { /** Represents the key used to retrieve the number of minutes between auto saves. */ private static final String KEY_AUTO_SAVE_INCREMENT = "autoSaveIncrement"; + /** Represents the key used to retrieve the parent directory used when creating new add-ons. */ + private static final String KEY_CREATE_ADD_ON_PARENT_DIR = "createAddOnParentDir"; + /** * The default value for the {@code KEY_AUTO_SAVE_INCREMENT} preference option. * @@ -2009,4 +2012,22 @@ public static String getExternalAddOnLibrariesPath() { public static void setExternalAddOnLibrariesPath(String path) { prefs.put(KEY_EXTERNAL_ADD_ON_LIBRARIES_PATH, path); } + + /** + * Returns the value of the preference used for the parent directory of the add-on creation + * + * @return the parent directory of the add-on creation + */ + public static String getCreateAddOnParentDir() { + return prefs.get(KEY_CREATE_ADD_ON_PARENT_DIR, ""); + } + + /** + * Sets the parent directory of the add-on creation + * + * @param path the parent directory of the add-on creation + */ + public static void setCreateAddOnParentDir(String path) { + prefs.put(KEY_CREATE_ADD_ON_PARENT_DIR, path); + } } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form index 1b124ebd47..d19f4f78b8 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form @@ -379,7 +379,9 @@ - + + + diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index 3179de74d0..a263adfb9d 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -40,6 +40,7 @@ import net.rptools.maptool.client.AppConstants; import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.swing.SwingUtil; import net.rptools.maptool.client.ui.JLabelHyperLinkListener; import net.rptools.maptool.client.ui.ViewAssetDialog; import net.rptools.maptool.events.MapToolEventBus; @@ -234,8 +235,12 @@ public void windowClosing(WindowEvent e) { e -> { setExternalAddOnControlsEnabled(enableExternalAddOnCheckBox.isSelected()); directoryTextField.setEnabled(enableExternalAddOnCheckBox.isSelected()); - new LibraryManager() - .setExternalLibrariesEnabled(enableExternalAddOnCheckBox.isSelected()); + try { + new LibraryManager() + .setExternalLibrariesEnabled(enableExternalAddOnCheckBox.isSelected()); + } catch (IOException ex) { + // do nothing + } AppPreferences.setExternalLibraryManagerEnabled(enableExternalAddOnCheckBox.isSelected()); }); @@ -270,15 +275,10 @@ private void setExternalAddOnControlsEnabled(boolean selected) { } private void createAddonSkeleton() { - // TODO: CDW - Implement - /* - LibraryManager library = new LibraryManager(); - try { - library.registerExternalAddOnLibrary( - new AddOnLibraryImporter().importFromDirectory(Path.of(directoryTextField.getText()))); - } catch (IOException e) { - MapTool.showError("library.import.ioError", e); - }**/ + var dialog = new CreateNewAddonDialog(); + dialog.pack(); + SwingUtil.centerOver(dialog, MapTool.getFrame()); + dialog.setVisible(true); } /** diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.form b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.form new file mode 100644 index 0000000000..43cd965d80 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.form @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java new file mode 100644 index 0000000000..d6e6d50555 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java @@ -0,0 +1,203 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.ui.addon; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.util.List; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.JTextPane; +import javax.swing.KeyStroke; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import net.rptools.maptool.client.AppPreferences; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.ui.addon.creator.NewAddOnBuilder; +import net.rptools.maptool.language.I18N; + +public class CreateNewAddonDialog extends JDialog { + + private JPanel contentPane; + private JButton buttonOK; + private JButton buttonCancel; + private JTextField nameTextField; + private JTextField versionTextField; + private JCheckBox eventsCheckBox; + private JTextPane descriptionTextPane; + private JButton directoryBrowseButton; + private JTextField namespaceTextField; + private JTextField gitURLTextField; + private JTextField websiteTextField; + private JTextField licenseTextField; + private JTextField authorsTextField; + private JTextField parentDirectoryTextField; + private JCheckBox slashCommandCheckBox; + private JCheckBox mtsPropCheckBox; + private JCheckBox udfCheckBox; + private JTextField shortDescTextBox; + private JTextField directoryTextField; + + public CreateNewAddonDialog() { + setContentPane(contentPane); + setModal(true); + getRootPane().setDefaultButton(buttonOK); + + buttonOK.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + onOK(); + } + }); + + buttonCancel.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + onCancel(); + } + }); + + // call onCancel() when cross is clicked + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener( + new WindowAdapter() { + public void windowClosing(WindowEvent e) { + onCancel(); + } + }); + + // call onCancel() on ESCAPE + contentPane.registerKeyboardAction( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + onCancel(); + } + }, + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + // browse button + directoryBrowseButton.addActionListener( + e -> { + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle(I18N.getText("library.dialog.create.parentDir.title")); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.showOpenDialog(MapTool.getFrame()); + if (chooser.getSelectedFile() != null) { + var path = chooser.getSelectedFile().getAbsolutePath(); + parentDirectoryTextField.setText(path); + AppPreferences.setCreateAddOnParentDir(path); + } + }); + + var docChangeListener = + new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + validateInputs(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + validateInputs(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + validateInputs(); + } + }; + + namespaceTextField.getDocument().addDocumentListener(docChangeListener); + nameTextField.getDocument().addDocumentListener(docChangeListener); + versionTextField.getDocument().addDocumentListener(docChangeListener); + parentDirectoryTextField.getDocument().addDocumentListener(docChangeListener); + directoryTextField.getDocument().addDocumentListener(docChangeListener); + authorsTextField.getDocument().addDocumentListener(docChangeListener); + licenseTextField.getDocument().addDocumentListener(docChangeListener); + shortDescTextBox.getDocument().addDocumentListener(docChangeListener); + descriptionTextPane.getDocument().addDocumentListener(docChangeListener); + + setDefaults(); + + validateInputs(); + } + + private void setDefaults() { + namespaceTextField.setText("net.some-example.addon"); + nameTextField.setText("Example Add-On"); + versionTextField.setText("0.0.1"); + shortDescTextBox.setText(I18N.getText("library.dialog.addon.create.shortDesc")); + descriptionTextPane.setText(I18N.getText("library.dialog.addon.create.longDesc")); + parentDirectoryTextField.setText(AppPreferences.getCreateAddOnParentDir()); + } + + private void validateInputs() { + buttonOK.setEnabled( + !namespaceTextField.getText().isEmpty() + && !nameTextField.getText().isEmpty() + && !versionTextField.getText().isEmpty() + && !parentDirectoryTextField.getText().isEmpty() + && !directoryTextField.getText().isEmpty() + && !authorsTextField.getText().isEmpty()); + } + + private void onOK() { + var parentDir = new File(parentDirectoryTextField.getText()); + if (!parentDir.exists()) { + JOptionPane.showMessageDialog( + this, I18N.getText("library.dialog.addon.create.noSuchDir", parentDir.toString())); + return; + } + var dir = parentDir.toPath().resolve(directoryTextField.getText()).toFile(); + if (dir.exists()) { + JOptionPane.showMessageDialog( + this, I18N.getText("library.dialog.addon.create.dirExists", dir.toString())); + return; + } + + var newAddon = + new NewAddOnBuilder() + .setName(nameTextField.getText()) + .setVersion(versionTextField.getText()) + .setNamespace(namespaceTextField.getText()) + .setGitURL(gitURLTextField.getText()) + .setWebsite(websiteTextField.getText()) + .setLicense(licenseTextField.getText()) + .setShortDescription(shortDescTextBox.getText()) + .setDescription(descriptionTextPane.getText()) + .setAuthors(List.of(authorsTextField.getText().split(","))) + .setCreateEvents(eventsCheckBox.isSelected()) + .setCreateSlashCommands(slashCommandCheckBox.isSelected()) + .setCreateUDFs(udfCheckBox.isSelected()) + .setCreateMTSProperties(mtsPropCheckBox.isSelected()) + .build(); + dispose(); + } + + private void onCancel() { + dispose(); + } +} diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java index 16f65c898d..8b6773776b 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java @@ -20,6 +20,7 @@ import javax.swing.border.EmptyBorder; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; +import net.rptools.maptool.client.MapTool; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.library.LibraryManager; import net.rptools.maptool.model.library.addon.ExternalLibraryInfo; @@ -36,7 +37,8 @@ public ExternalAddOnImportCellEditor() { try { new LibraryManager().importFromExternal(info.libraryInfo().namespace()); } catch (IOException ex) { - throw new RuntimeException(ex); // TODO: CDW + MapTool.showError( + I18N.getText("library.dialog.import.failed", info.libraryInfo().namespace())); } stopCellEditing(); }); diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java new file mode 100644 index 0000000000..2e08773128 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java @@ -0,0 +1,34 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.ui.addon.creator; + +import java.util.List; + +public record NewAddOn( + String name, + String version, + String namespace, + String gitURL, + String website, + String license, + String shortDescription, + String description, + List authors, + boolean createEvents, + boolean createSlashCommands, + boolean createMTSProperties, + boolean createUDFs, + String readme, + String licenseText) {} diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java new file mode 100644 index 0000000000..0595b5bea6 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java @@ -0,0 +1,131 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.ui.addon.creator; + +import java.util.ArrayList; +import java.util.List; +import net.rptools.maptool.language.I18N; + +public class NewAddOnBuilder { + + private String name; + private String version; + private String namespace; + private String gitURL; + private String website; + private String license; + + private String shortDescription; + private String description; + private List authors = new ArrayList<>(); + + private boolean createEvents; + private boolean createSlashCommands; + private boolean createMTSProperties; + private boolean createUDFs; + + public NewAddOnBuilder setName(String name) { + this.name = name; + return this; + } + + public NewAddOnBuilder setVersion(String version) { + this.version = version; + return this; + } + + public NewAddOnBuilder setNamespace(String namespace) { + this.namespace = namespace; + return this; + } + + public NewAddOnBuilder setGitURL(String gitURL) { + this.gitURL = gitURL; + return this; + } + + public NewAddOnBuilder setWebsite(String website) { + this.website = website; + return this; + } + + public NewAddOnBuilder setLicense(String license) { + this.license = license; + return this; + } + + public NewAddOnBuilder setShortDescription(String shortDescription) { + this.shortDescription = shortDescription; + return this; + } + + public NewAddOnBuilder setDescription(String description) { + this.description = description; + return this; + } + + public NewAddOnBuilder setAuthors(List authors) { + this.authors.clear(); + this.authors.addAll(authors); + return this; + } + + public NewAddOnBuilder setCreateEvents(boolean events) { + this.createEvents = events; + return this; + } + + public NewAddOnBuilder setCreateSlashCommands(boolean slashCommands) { + this.createSlashCommands = slashCommands; + return this; + } + + public NewAddOnBuilder setCreateMTSProperties(boolean mtsProperties) { + this.createMTSProperties = mtsProperties; + return this; + } + + public NewAddOnBuilder setCreateUDFs(boolean udfs) { + this.createUDFs = udfs; + return this; + } + + public NewAddOn build() { + return new NewAddOn( + name, + version, + namespace, + gitURL, + website, + license, + shortDescription, + description, + authors, + createEvents, + createSlashCommands, + createMTSProperties, + createUDFs, + getReadMe(), + getLicenseText()); + } + + private String getReadMe() { + return I18N.getText("library.dialog.readme.default"); + } + + private String getLicenseText() { + return I18N.getText("library.dialog.license.default"); + } +} diff --git a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java index bd8d589ec0..f9624e356d 100644 --- a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java @@ -73,8 +73,8 @@ public class LibraryManager { new AddOnSlashCommandManager(); /** - * Initializes the library manager. This method should be called after - * instantiation of the library manager. + * Initializes the library manager. This method should be called after instantiation of the + * library manager. */ public static void init() { libraryTokenManager.init(); @@ -106,7 +106,7 @@ public boolean externalLibrariesEnabled() { * * @param enabled if external add-on libraries are enabled. */ - public void setExternalLibrariesEnabled(boolean enabled) { + public void setExternalLibrariesEnabled(boolean enabled) throws IOException { addOnLibraryManager.setExternalLibrariesEnabled(enabled); } @@ -123,7 +123,6 @@ public Path getEternalLibraryPath() { * Sets the path to the external add-on libraries. * * @param path the path to the external add-on libraries. - * * @throws IOException if an error occurs while setting the path. */ public void setExternalLibraryPath(Path path) throws IOException { @@ -264,7 +263,6 @@ public boolean reregisterAddOnLibrary(AddOnLibrary addOnLibrary) { * MapTool. To make the add-on available to MapTool, use {@link #importFromExternal(String)} * * @param path The path of the add-on to register. - * * @throws IOException if an error occurs while registering the add-on. */ public void registerExternalAddOnLibrary(Path path) throws IOException { diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java index 9374f25e4c..f984c57a56 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java @@ -211,27 +211,21 @@ public CompletableFuture> getLegacyEventTargets(String eventName) { .collect(Collectors.toSet())); } - /** - * Initializes the add-on library manager. - * - */ + /** Initializes the add-on library manager. */ public void init() { externalAddOnLibraryManager = new ExternalAddOnLibraryManager(this); - externalAddOnLibraryManager.init(); - String path = AppPreferences.getExternalAddOnLibrariesPath(); - if (path != null && !path.isEmpty()) { + + try { + externalAddOnLibraryManager.setExternalLibraryPath(Path.of(path)); + externalAddOnLibraryManager.setEnabled(AppPreferences.getExternalLibraryManagerEnabled()); + externalAddOnLibraryManager.init(); + } catch (IOException e) { + MapTool.showError("Error setting external library path", e); try { - externalAddOnLibraryManager.setExternalLibraryPath(Path.of(path)); - externalAddOnLibraryManager.setEnabled(AppPreferences.getExternalLibraryManagerEnabled()); - } catch (IOException e) { - MapTool.showError("Error setting external library path", e); - try { - externalAddOnLibraryManager.setExternalLibraryPath(null); - externalAddOnLibraryManager.setEnabled(false); - } catch (IOException ex) { - // Do nothing. - } + externalAddOnLibraryManager.setEnabled(false); + } catch (IOException ex) { + // Do nothing as it shouldn't happen, but if it does there is no action we can take } } } @@ -273,7 +267,6 @@ public boolean externalLibrariesEnabled() { * Sets if external add-on libraries are enabled. * * @param enabled if external add-on libraries are enabled. - * * @throws IOException if an I/O error occurs. */ public void setExternalLibrariesEnabled(boolean enabled) throws IOException { @@ -293,7 +286,6 @@ public Path getExternalLibraryPath() { * Sets the path to the external add-on libraries. * * @param path the path to the external add-on libraries. - * * @throws IOException if an I/O error occurs. */ public void setExternalLibraryPath(Path path) throws IOException { @@ -304,7 +296,6 @@ public void setExternalLibraryPath(Path path) throws IOException { * Registers the add-on library as an external library. * * @param path The path to the library. - * * @throws IOException if an I/O error occurs. */ public void registerExternalLibrary(Path path) throws IOException { diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java index 34be238ba9..297b55e9da 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java @@ -23,7 +23,9 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; +import net.rptools.maptool.client.MapTool; import net.rptools.maptool.events.MapToolEventBus; +import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.library.AddOnsAddedEvent; import net.rptools.maptool.model.library.AddOnsRemovedEvent; import net.rptools.maptool.model.library.ExternalAddonsUpdateEvent; @@ -43,11 +45,14 @@ public class ExternalAddOnLibraryManager { private final Map namespaceInfoMap = new ConcurrentHashMap<>(); /** Is the external add-on library manager enabled. */ - private boolean isEnabled = false; + private boolean enabled = false; /** The path to the external add-on libraries. */ private Path externalLibraryPath = null; + /** Is the external add-on library manager initialised. */ + private boolean initialised = false; + /** Directory watcher for watching the external add-on library directory. */ private DirectoryWatcher directoryWatcher; @@ -64,8 +69,26 @@ public ExternalAddOnLibraryManager(AddOnLibraryManager addOnLibraryManager) { this.addOnLibraryManager = addOnLibraryManager; } - /** Initializes the external add-on library manager. */ - public void init() { + /** + * Initializes the external add-on library manager. It is safe to call {@see + * setExternalLibraryPath(Path)} and {@see setEnabled(boolean)} before calling this method, but no + * directory watching will occur until this method is called. + * + * @throws IOException if an error occurs. + * @throws IllegalStateException if the external add-on library manager has already been + * initialised. + */ + public void init() throws IOException { + try { + lock.lock(); + if (initialised) { + throw new IllegalStateException("External add-on library manager already initialised"); + } + initialised = true; + startWatching(); + } finally { + lock.unlock(); + } var eventBus = new MapToolEventBus().getMainEventBus(); eventBus.register(this); } @@ -174,25 +197,25 @@ public void refreshExternalAddOnLibrary(Path path) throws IOException { * Registers an external add-on library. * * @param path the path to the add-on library. - * * @throws IOException if an error occurs. */ public void registerExternalAddOnLibrary(Path path) throws IOException { - var lib = new AddOnLibraryImporter().getLibraryInfoFromDirectory(path); - if (lib == null) { - return; - } - var info = - new ExternalLibraryInfo( - lib.namespace(), - lib, - false, - false, - path, - externalLibraryPath.relativize(path).toString()); - registerExternalAddOnLibrary(info); - var eventBus = new MapToolEventBus().getMainEventBus(); - eventBus.post(new ExternalAddonsUpdateEvent()); + var lib = new AddOnLibraryImporter().getLibraryInfoFromDirectory(path); + if (lib == null) { + return; + } + boolean isInstalled = addOnLibraryManager.isNamespaceRegistered(lib.namespace()); + var info = + new ExternalLibraryInfo( + lib.namespace(), + lib, + false, + isInstalled, + path, + externalLibraryPath.relativize(path).toString()); + registerExternalAddOnLibrary(info); + var eventBus = new MapToolEventBus().getMainEventBus(); + eventBus.post(new ExternalAddonsUpdateEvent()); } /** @@ -212,7 +235,7 @@ public List getLibraries() { public boolean isEnabled() { try { lock.lock(); - return isEnabled; + return enabled; } finally { lock.unlock(); } @@ -222,14 +245,13 @@ public boolean isEnabled() { * Sets the enabled state of the external add-on library manager. * * @param enabled the enabled state. - * * @throws IOException if an error occurs. */ public void setEnabled(boolean enabled) throws IOException { try { lock.lock(); - if (isEnabled != enabled) { - isEnabled = enabled; + if (this.enabled != enabled) { + this.enabled = enabled; if (enabled) { startWatching(); } else { @@ -259,7 +281,6 @@ public Path getExternalLibraryPath() { * Sets the path to the external add-on libraries. * * @param path the path to the external add-on libraries. - * * @throws IOException if an error occurs. */ public void setExternalLibraryPath(Path path) throws IOException { @@ -270,7 +291,7 @@ public void setExternalLibraryPath(Path path) throws IOException { lock.lock(); externalLibraryPath = path; stopWatching(); - if (path != null) { + if (path != null && enabled) { startWatching(); } } finally { @@ -300,7 +321,7 @@ private void startWatching() throws IOException { try { lock.lock(); refreshAll(); - if (isEnabled && externalLibraryPath != null && Files.exists(externalLibraryPath)) { + if (enabled && externalLibraryPath != null && Files.exists(externalLibraryPath)) { if (directoryWatcher != null) { directoryWatcher.watchAsync(); } else { @@ -319,13 +340,17 @@ private void startWatching() throws IOException { * @throws IOException if an error occurs. */ private void refreshAll() throws IOException { - if (externalLibraryPath == null) { + if (!initialised || !enabled || externalLibraryPath == null) { return; } File[] directories = externalLibraryPath.toFile().listFiles(File::isDirectory); if (directories != null) { for (File directory : directories) { - registerExternalAddOnLibrary(directory.toPath()); + try { + registerExternalAddOnLibrary(directory.toPath()); + } catch (IOException e) { + MapTool.showError(I18N.getText("library.dialog.read.failed", directory)); + } } } var eventBus = new MapToolEventBus().getMainEventBus(); @@ -338,7 +363,7 @@ private void refreshAll() throws IOException { * @param namespace the namespace of the add-on library to make available. */ public void importLibrary(String namespace) throws IOException { - if (isEnabled) { + if (enabled) { var libInfo = namespaceInfoMap.get(namespace.toLowerCase()); if (libInfo != null) { var lib = new AddOnLibraryImporter().importFromDirectory(libInfo.backingDirectory()); @@ -358,23 +383,31 @@ private DirectoryWatcher createDirectoryWatcher() throws IOException { .path(externalLibraryPath) .listener( event -> { - int basePathNameCount = externalLibraryPath.getNameCount(); - var path = event.path(); - if (path.getNameCount() <= basePathNameCount) { - return; - } - if (!path.startsWith(externalLibraryPath)) { - return; - } - path = externalLibraryPath.resolve(path.getName(basePathNameCount)); - switch (event.eventType()) { - case CREATE -> registerExternalAddOnLibrary(path); - case DELETE -> deregisterExternalAddOnLibrary(path); - case MODIFY -> refreshExternalAddOnLibrary(path); + try { + int basePathNameCount = externalLibraryPath.getNameCount(); + var path = event.path(); + if (path.getNameCount() <= basePathNameCount) { + return; + } + if (!path.startsWith(externalLibraryPath)) { + return; + } + path = externalLibraryPath.resolve(path.getName(basePathNameCount)); + switch (event.eventType()) { + case CREATE -> registerExternalAddOnLibrary(path); + case DELETE -> { + if (path.toFile().exists()) { + refreshExternalAddOnLibrary(path); + } else { + deregisterExternalAddOnLibrary(path); + } + } + case MODIFY -> refreshExternalAddOnLibrary(path); + } + } catch (IOException e) { + MapTool.showError(I18N.getText("library.dialog.read.failed", event.path())); } }) .build(); } } - -// TODO: CDW dont crash on invalid library file diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 7abaa3ccf7..85b775f775 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2987,11 +2987,23 @@ library.dialog.addon.updated = Updated library.dialog.addon.refresh = Refresh library.dialog.addon.createNew = Create New Add-On library.dialog.addon.export = Export Add-On -library.dialog.addon.import = Import Add-On library.dialog.directory.label = Add On Directory -library.dialog.directory.refresh = Refresh Directory Contents - - +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = ### Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On # Game Data data.error.cantConvertTo = Can''t convert {0} to {1}. From e5bf63d1c6c3623940eea1857c1d18c0b19c344b Mon Sep 17 00:00:00 2001 From: jmr3366 Date: Mon, 2 Sep 2024 07:59:04 -0400 Subject: [PATCH 013/146] Feature #483 https://discord.com/channels/296230822262865920/296658523725496320/578194944871759909 rest functions use macro.return instead of ParserException. --- .../maptool/client/functions/TokenImage.java | 34 +++++++++++++++++-- .../rptools/maptool/language/i18n.properties | 2 ++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java index d77c736293..5c7710469e 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java @@ -18,6 +18,7 @@ import java.awt.Image; import java.math.BigDecimal; import java.util.List; +import com.jidesoft.utils.Base64; import net.rptools.lib.MD5Key; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; @@ -71,7 +72,8 @@ private TokenImage() { "getImage", "setTokenOpacity", "getAssetProperties", - "getTokenOpacity"); + "getTokenOpacity", + "createAsset"); } /** @@ -171,6 +173,35 @@ public Object childEvaluate( } } + StringBuilder assetId = new StringBuilder("asset://"); + if (functionName.equalsIgnoreCase("createAsset")) { + FunctionUtil.checkNumberParam(functionName, args, 2, 2); + String imageName = args.get(0).toString(); + String imageString = args.get(1).toString(); + if(imageName == "" || imageString == "") { + throw new ParserException( + I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); + } else { + if (imageString.length() > 8) { + byte[] imageBytes = Base64.decode(imageString); + String imageCheck = new String(imageBytes, 0, 4); + /* header check for: webp || jpg || png */ + if (imageCheck.equals("RIFF") || imageCheck.equals("ÿØÿà") || imageCheck.equals("‰PNG")) { + Asset asset = Asset.createImageAsset(imageName, imageBytes); + AssetManager.putAsset(asset); + assetId.append(asset.getMD5Key().toString()); + return assetId; + } else { + throw new ParserException( + I18N.getText("dragdrop.unsupportedType", functionName)); + } + } else { + throw new ParserException( + I18N.getText("macro.function.general.wrongParamType", functionName)); + } + } + } + /* getImage, getTokenImage, getTokenPortrait, or getTokenHandout */ int indexSize = -1; // by default, no size added to asset id if (functionName.equalsIgnoreCase("getImage")) { @@ -195,7 +226,6 @@ public Object childEvaluate( token = FunctionUtil.getTokenFromParam(resolver, functionName, args, 1, 2); } - StringBuilder assetId = new StringBuilder("asset://"); if (functionName.equalsIgnoreCase("getTokenImage")) { if (token.getImageAssetId() == null) { return ""; diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 4ebff9e42d..69439b4200 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2010,6 +2010,8 @@ macro.function.general.unknownProperty = Error executing "{0}": the macro.function.general.unknownTokenOnMap = Error executing "{0}": the token name or id "{1}" is unknown on map "{2}". macro.function.general.wrongNumParam = Function "{0}" requires exactly {1} parameters; {2} were provided. macro.function.general.listCannotBeEmpty = {0}: string list at argument {1} cannot be empty +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Invalid metric type "{0}". From dcaff0d47a66fd24840e90bffbd85d36a63539b3 Mon Sep 17 00:00:00 2001 From: jmr3366 Date: Mon, 2 Sep 2024 08:39:34 -0400 Subject: [PATCH 014/146] Update TokenImage.java --- .../rptools/maptool/client/functions/TokenImage.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java index 5c7710469e..d68cea53c5 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java @@ -15,10 +15,10 @@ package net.rptools.maptool.client.functions; import com.google.gson.JsonObject; +import com.jidesoft.utils.Base64; import java.awt.Image; import java.math.BigDecimal; import java.util.List; -import com.jidesoft.utils.Base64; import net.rptools.lib.MD5Key; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; @@ -178,9 +178,9 @@ public Object childEvaluate( FunctionUtil.checkNumberParam(functionName, args, 2, 2); String imageName = args.get(0).toString(); String imageString = args.get(1).toString(); - if(imageName == "" || imageString == "") { + if (imageName == "" || imageString == "") { throw new ParserException( - I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); + I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); } else { if (imageString.length() > 8) { byte[] imageBytes = Base64.decode(imageString); @@ -192,12 +192,11 @@ public Object childEvaluate( assetId.append(asset.getMD5Key().toString()); return assetId; } else { - throw new ParserException( - I18N.getText("dragdrop.unsupportedType", functionName)); + throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); } } else { throw new ParserException( - I18N.getText("macro.function.general.wrongParamType", functionName)); + I18N.getText("macro.function.general.wrongParamType", functionName)); } } } From 95026654810c00370e820db99a015a2fead75b81 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Wed, 4 Sep 2024 20:15:40 +0930 Subject: [PATCH 015/146] More work on addon creation --- .../ui/addon/creator/NewAddOnBuilder.java | 4 +- .../ui/addon/creator/NewAddOnCreator.java | 104 ++++++++++++++++++ .../rptools/maptool/language/i18n.properties | 2 + 3 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java index 0595b5bea6..dc08b1bdea 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java @@ -122,10 +122,10 @@ public NewAddOn build() { } private String getReadMe() { - return I18N.getText("library.dialog.readme.default"); + return I18N.getText("library.dialog.addon.create.readeMe"); } private String getLicenseText() { - return I18N.getText("library.dialog.license.default"); + return I18N.getText("library.dialog.addon.create.licenseText"); } } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java new file mode 100644 index 0000000000..d60ef20c56 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java @@ -0,0 +1,104 @@ +package net.rptools.maptool.client.ui.addon.creator; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import net.rptools.maptool.language.I18N; + +public class NewAddOnCreator { + + private static final String LIBRARY_FILE = "library.json"; + private static final String EVENTS_FILE = "events.json"; + private static final String SLASH_COMMAND_FILE = "slash_commands.json"; + private static final String MTS_PROPERTIES_FILE = "mts_properties.json"; + private static final String UDF_FILE = "functions.json"; + private static final String README_FILE = "README.md"; + private static final String LICENSE_FILE = "license.txt"; + + private static final String LIBRARY_DIRECTORY = "library"; + private static final String MTSCRIPT_DIRECTORY = "mtscript"; + private static final String PUBLIC_DIRECTORY = "public"; + + + private final NewAddOn addOn; + + public NewAddOnCreator(NewAddOn newAddOn) { + addOn = newAddOn; + } + + + public void create(Path path) throws IOException { + createAddOnDirectories(path); + createLibraryFile(path); + if (addOn.createEvents()) { + createEvents(path); + } + if (addOn.createSlashCommands()) { + createSlashCommands(path); + } + if (addOn.createMTSProperties()) { + createMTSProperties(path); + } + if (addOn.createUDFs()) { + createUDFs(path); + } + createReadmeFile(path); + createLicenseFile(path); + } + + private void createEvents(Path path) { + + } + + private void createLibraryFile(Path path) throws IOException { + var libraryJson = new JsonObject(); + libraryJson.addProperty("name", addOn.name()); + libraryJson.addProperty("version", addOn.version()); + libraryJson.addProperty("namespace", addOn.namespace()); + if (addOn.gitURL() != null && !addOn.gitURL().isEmpty()) { + libraryJson.addProperty("gitURL", addOn.gitURL()); + } + if (addOn.website() != null && !addOn.website().isEmpty()) { + libraryJson.addProperty("website", addOn.website()); + } + var jsonAuthors = new JsonArray(); + addOn.authors().forEach(jsonAuthors::add); + libraryJson.add("authors", jsonAuthors); + if (addOn.license() != null && !addOn.license().isEmpty()) { + libraryJson.addProperty("license", addOn.license()); + } + libraryJson.addProperty("shortDescription", addOn.shortDescription()); + libraryJson.addProperty("description", addOn.description()); + try { + var libraryPath = path.resolve(LIBRARY_FILE); + var writer = new FileWriter(libraryPath.toFile()); + writer.write(libraryJson.toString()); + writer.close(); + } catch (IOException e) { + throw new IOException(I18N.getText("library.dialog.failedToCreateFile", LIBRARY_FILE), e); + } + + } + + private void createAddOnDirectories(Path path) throws IOException { + Path libraryPath = path.resolve(LIBRARY_DIRECTORY); + Path mtscriptPath = libraryPath.resolve(MTSCRIPT_DIRECTORY); + Path publicPath = libraryPath.resolve(PUBLIC_DIRECTORY); + Path mtscriptPublicPath = mtscriptPath.resolve(PUBLIC_DIRECTORY); + + createAddOnDirectory(path); + createAddOnDirectory(libraryPath); + createAddOnDirectory(mtscriptPath); + createAddOnDirectory(mtscriptPublicPath); + createAddOnDirectory(publicPath); + } + + + private void createAddOnDirectory(Path path) throws IOException { + if (!path.toFile().mkdirs()) { + throw new IOException(I18N.getText("library.dialog.failedToCreateDir", path.toString())); + } + } +} diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 85b775f775..1af2a89085 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -3004,6 +3004,8 @@ library.dialog.addon.create.longDesc = Add-on Long Description library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. # Game Data data.error.cantConvertTo = Can''t convert {0} to {1}. From e8a0d0d5a92b1b8cb0452e18a5d4d5f62fd1fde3 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Sun, 8 Sep 2024 23:30:57 +0930 Subject: [PATCH 016/146] Create addon skeleton --- build.gradle | 3 - .../client/ui/addon/CreateNewAddonDialog.form | 3 +- .../client/ui/addon/CreateNewAddonDialog.java | 12 ++ .../ui/addon/creator/NewAddOnCreator.java | 202 ++++++++++++++---- .../model/library/addon/AddOnLibrary.java | 10 +- .../rptools/maptool/language/i18n.properties | 3 + 6 files changed, 183 insertions(+), 50 deletions(-) diff --git a/build.gradle b/build.gradle index 6b02779417..b3249fb704 100644 --- a/build.gradle +++ b/build.gradle @@ -485,9 +485,6 @@ dependencies { // Built In Add-on Libraries implementation 'com.github.RPTools:maptool-builtin-addons:1.3' - // File watcher library to work around some inconsistencies with java.nio.file.WatchService - implementation 'io.methvin:directory-watcher:0.18.0' - // For advanced dice roller implementation 'com.github.RPTools:advanced-dice-roller:1.0.3' } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.form b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.form index 43cd965d80..949190019e 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.form +++ b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.form @@ -3,7 +3,7 @@ - + @@ -262,6 +262,7 @@ + diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java index d6e6d50555..71c8cd6902 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java @@ -20,6 +20,7 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; +import java.io.IOException; import java.util.List; import javax.swing.JButton; import javax.swing.JCheckBox; @@ -36,6 +37,7 @@ import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.ui.addon.creator.NewAddOnBuilder; +import net.rptools.maptool.client.ui.addon.creator.NewAddOnCreator; import net.rptools.maptool.language.I18N; public class CreateNewAddonDialog extends JDialog { @@ -195,6 +197,16 @@ private void onOK() { .setCreateMTSProperties(mtsPropCheckBox.isSelected()) .build(); dispose(); + if (!dir.mkdirs()) { + JOptionPane.showMessageDialog( + this, I18N.getText("library.dialog.failedToCreateDir", dir.toString())); + return; + } + try { + new NewAddOnCreator(newAddon).create(dir.toPath()); + } catch (IOException e) { + JOptionPane.showMessageDialog(this, e.getMessage()); + } } private void onCancel() { diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java index d60ef20c56..0cad0a230e 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java @@ -2,93 +2,213 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import com.google.protobuf.util.JsonFormat; import java.io.FileWriter; import java.io.IOException; import java.nio.file.Path; import net.rptools.maptool.language.I18N; +import net.rptools.maptool.model.library.addon.AddOnLibrary; +import net.rptools.maptool.model.library.addon.AddOnLibraryImporter; +import net.rptools.maptool.model.library.proto.AddOnLibraryDto; +import net.rptools.maptool.model.library.proto.AddOnLibraryEventsDto; +import net.rptools.maptool.model.library.proto.AddonSlashCommandsDto; +import net.rptools.maptool.model.library.proto.AddonSlashCommandsDto.AddOnSlashCommand; +import net.rptools.maptool.model.library.proto.MTScriptPropertiesDto; public class NewAddOnCreator { - private static final String LIBRARY_FILE = "library.json"; - private static final String EVENTS_FILE = "events.json"; - private static final String SLASH_COMMAND_FILE = "slash_commands.json"; - private static final String MTS_PROPERTIES_FILE = "mts_properties.json"; - private static final String UDF_FILE = "functions.json"; private static final String README_FILE = "README.md"; private static final String LICENSE_FILE = "license.txt"; - private static final String LIBRARY_DIRECTORY = "library"; - private static final String MTSCRIPT_DIRECTORY = "mtscript"; - private static final String PUBLIC_DIRECTORY = "public"; private final NewAddOn addOn; + private Path addOnPath; + private Path libraryPath; + private Path mtscriptPath; + private Path publicPath; + private Path mtscriptPublicPath; + public NewAddOnCreator(NewAddOn newAddOn) { addOn = newAddOn; } public void create(Path path) throws IOException { - createAddOnDirectories(path); - createLibraryFile(path); + addOnPath = path; + libraryPath = path.resolve(AddOnLibraryImporter.LIBRARY_INFO_FILE); + mtscriptPath = libraryPath.resolve(AddOnLibrary.MTSCRIPT_DIR); + publicPath = libraryPath.resolve(AddOnLibrary.URL_PUBLIC_DIR); + mtscriptPublicPath = mtscriptPath.resolve(AddOnLibrary.MTSCRIPT_PUBLIC_DIR); + createAddOnDirectories(); + createLibraryFile(); + if (addOn.createEvents()) { - createEvents(path); + createEvents(); } + if (addOn.createSlashCommands()) { - createSlashCommands(path); + createSlashCommands(); } if (addOn.createMTSProperties()) { - createMTSProperties(path); + createMTSProperties(); } - if (addOn.createUDFs()) { - createUDFs(path); + createReadmeFile(); + createLicenseFile(); + } + + private void createLicenseFile() throws IOException { + try { + var licensePath = addOnPath.resolve(LICENSE_FILE); + var writer = new FileWriter(licensePath.toFile()); + writer.write(addOn.licenseText()); + writer.close(); + } catch (IOException e) { + throw new IOException(I18N.getText("library.dialog.failedToCreateFile", + LICENSE_FILE), e); } - createReadmeFile(path); - createLicenseFile(path); } - private void createEvents(Path path) { + private void createReadmeFile() throws IOException { + try { + var readmePath = addOnPath.resolve(README_FILE); + var writer = new FileWriter(readmePath.toFile()); + writer.write(addOn.readme()); + writer.close(); + } catch (IOException e) { + throw new IOException(I18N.getText("library.dialog.failedToCreateFile", + README_FILE), e); + } + } + + private void createMTSProperties() throws IOException { + var builder = MTScriptPropertiesDto.newBuilder(); + var propertiesBuilderAutoExec = MTScriptPropertiesDto.Property.newBuilder(); + propertiesBuilderAutoExec.setFilename("public/auto_exec.mts"); + propertiesBuilderAutoExec.setAutoExecute(true); + propertiesBuilderAutoExec.setDescription(I18N.getText("library.dialog.create.autoExecDesc")); + + var propertiesBuilderNoAutoExec = MTScriptPropertiesDto.Property.newBuilder(); + propertiesBuilderNoAutoExec.setFilename("public/no_auto_exec.mts"); + propertiesBuilderNoAutoExec.setAutoExecute(false); + propertiesBuilderNoAutoExec.setDescription(I18N.getText("library.dialog.create.noAutoExecDesc")); + + builder.addProperties(propertiesBuilderAutoExec); + builder.addProperties(propertiesBuilderNoAutoExec); + + try { + var mtsPropertiesPath = mtscriptPath.resolve(AddOnLibraryImporter.MACROSCRIPT_PROPERTY_FILE); + var writer = new FileWriter(mtsPropertiesPath.toFile()); + writer.write(JsonFormat.printer().includingDefaultValueFields().print(builder)); + writer.close(); + } catch (IOException e) { + throw new IOException(I18N.getText("library.dialog.failedToCreateFile", + AddOnLibraryImporter.MACROSCRIPT_PROPERTY_FILE), e); + } + // TODO: CDW Create MTS properties macros + } + + private void createSlashCommands() throws IOException { + var builder = AddonSlashCommandsDto.newBuilder(); + var slashCommandBuilder = AddOnSlashCommand.newBuilder(); + slashCommandBuilder.setName("exampleSlash"); + slashCommandBuilder.setDescription(I18N.getText("library.dialog.create.exampleSlashCmdDesc")); + slashCommandBuilder.setCommand("testSlash.mts"); + builder.addSlashCommands(slashCommandBuilder); + + try { + var slashCommandPath = mtscriptPath.resolve(AddOnLibraryImporter.SLASH_COMMAND_FILE); + var writer = new FileWriter(slashCommandPath.toFile()); + writer.write(JsonFormat.printer().includingDefaultValueFields().print(builder)); + writer.close(); + } catch (IOException e) { + throw new IOException(I18N.getText("library.dialog.failedToCreateFile", + AddOnLibraryImporter.SLASH_COMMAND_FILE), e); + } + // TODO: CDW Create Slash command macro } - private void createLibraryFile(Path path) throws IOException { - var libraryJson = new JsonObject(); - libraryJson.addProperty("name", addOn.name()); - libraryJson.addProperty("version", addOn.version()); - libraryJson.addProperty("namespace", addOn.namespace()); + private void createEvents() throws IOException { + // Add init events + var builder = AddOnLibraryEventsDto.newBuilder(); + var onFirstInitMTSBuilder = AddOnLibraryEventsDto.Events.newBuilder(); + onFirstInitMTSBuilder.setName("onFirstInit"); + onFirstInitMTSBuilder.setMts("onFirstInit.mts"); + builder.addEvents(onFirstInitMTSBuilder); + var onInitMTSBuilder = AddOnLibraryEventsDto.Events.newBuilder(); + onInitMTSBuilder.setName("onInit"); + onInitMTSBuilder.setMts("onInit.mts"); + builder.addEvents(onInitMTSBuilder); + + // Add legacy events + var legacyEventsBuilder = AddOnLibraryEventsDto.newBuilder(); + var onInitiativeChangeRequestBuilder = AddOnLibraryEventsDto.Events.newBuilder(); + onInitiativeChangeRequestBuilder.setName("onInitiativeChangeRequest"); + onInitiativeChangeRequestBuilder.setMts("onInitiativeChangeRequest.mts"); + var onInitiativeChangeBuilder = AddOnLibraryEventsDto.Events.newBuilder(); + onInitiativeChangeBuilder.setName("onInitiativeChange"); + onInitiativeChangeBuilder.setMts("onInitiativeChange.mts"); + legacyEventsBuilder.addLegacyEvents(onInitiativeChangeBuilder); + var onTokenMoveBuilder = AddOnLibraryEventsDto.Events.newBuilder(); + onTokenMoveBuilder.setName("onTokenMove"); + onTokenMoveBuilder.setMts("onTokenMove.mts"); + legacyEventsBuilder.addLegacyEvents(onTokenMoveBuilder); + var onMultipleTokensMoveBuilder = AddOnLibraryEventsDto.Events.newBuilder(); + onMultipleTokensMoveBuilder.setName("onMultipleTokensMove"); + onMultipleTokensMoveBuilder.setMts("onMultipleTokensMove.mts"); + legacyEventsBuilder.addLegacyEvents(onMultipleTokensMoveBuilder); + + // Add events to builder + builder.addAllEvents(legacyEventsBuilder.getLegacyEventsList()); + try { + var eventsPath = mtscriptPath.resolve(AddOnLibraryImporter.EVENT_PROPERTY_FILE); + var writer = new FileWriter(eventsPath.toFile()); + writer.write(JsonFormat.printer().includingDefaultValueFields().print(builder)); + writer.close(); + } catch (IOException e) { + throw new IOException(I18N.getText("library.dialog.failedToCreateFile", + AddOnLibraryImporter.EVENT_PROPERTY_FILE), e); + } + + // TODO: CDW Create event macros + + } + + private void createLibraryFile() throws IOException { + var builder = AddOnLibraryDto.newBuilder(); + builder.setName(addOn.name()); + builder.setVersion(addOn.version()); + builder.setNamespace(addOn.namespace()); + builder.addAllAuthors(addOn.authors()); if (addOn.gitURL() != null && !addOn.gitURL().isEmpty()) { - libraryJson.addProperty("gitURL", addOn.gitURL()); + builder.setGitUrl(addOn.gitURL()); } if (addOn.website() != null && !addOn.website().isEmpty()) { - libraryJson.addProperty("website", addOn.website()); + builder.setWebsite(addOn.website()); } - var jsonAuthors = new JsonArray(); - addOn.authors().forEach(jsonAuthors::add); - libraryJson.add("authors", jsonAuthors); if (addOn.license() != null && !addOn.license().isEmpty()) { - libraryJson.addProperty("license", addOn.license()); + builder.setLicense(addOn.license()); } - libraryJson.addProperty("shortDescription", addOn.shortDescription()); - libraryJson.addProperty("description", addOn.description()); + builder.setShortDescription(addOn.shortDescription()); + builder.setDescription(addOn.description()); + builder.setLicenseFile(LICENSE_FILE); + builder.setReadMeFile(README_FILE); try { - var libraryPath = path.resolve(LIBRARY_FILE); + var libraryPath = addOnPath.resolve(AddOnLibraryImporter.LIBRARY_INFO_FILE); var writer = new FileWriter(libraryPath.toFile()); - writer.write(libraryJson.toString()); + writer.write(JsonFormat.printer().includingDefaultValueFields().print(builder)); writer.close(); } catch (IOException e) { - throw new IOException(I18N.getText("library.dialog.failedToCreateFile", LIBRARY_FILE), e); + throw new IOException(I18N.getText("library.dialog.failedToCreateFile", + AddOnLibraryImporter.LIBRARY_INFO_FILE), e); } } - private void createAddOnDirectories(Path path) throws IOException { - Path libraryPath = path.resolve(LIBRARY_DIRECTORY); - Path mtscriptPath = libraryPath.resolve(MTSCRIPT_DIRECTORY); - Path publicPath = libraryPath.resolve(PUBLIC_DIRECTORY); - Path mtscriptPublicPath = mtscriptPath.resolve(PUBLIC_DIRECTORY); - - createAddOnDirectory(path); + private void createAddOnDirectories() throws IOException { + createAddOnDirectory(addOnPath); createAddOnDirectory(libraryPath); createAddOnDirectory(mtscriptPath); createAddOnDirectory(mtscriptPublicPath); diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java index d3994cca7a..9d2264c417 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java @@ -68,10 +68,10 @@ public class AddOnLibrary implements Library { /** The name of the event for first time initialization. */ - private static final String FIRST_INIT_EVENT = "onFirstInit"; + public static final String FIRST_INIT_EVENT = "onFirstInit"; /** The name of the event for initialization. */ - private static final String INIT_EVENT = "onInit"; + public static final String INIT_EVENT = "onInit"; /** The prefix for the name of the JavaScript context for this addon. */ private static final String JS_CONTEXT_PREFIX = "addon:"; @@ -80,13 +80,13 @@ public class AddOnLibrary implements Library { private record MTScript(String path, boolean autoExecute, String description, MD5Key md5Key) {} /** The directory where the files exposed URI are stored. */ - private static final String URL_PUBLIC_DIR = "public/"; + public static final String URL_PUBLIC_DIR = "public/"; /** The directory where MT MacroScripts are stored. */ - private static final String MTSCRIPT_DIR = "mtscript/"; + public static final String MTSCRIPT_DIR = "mtscript/"; /** The directory where public MT MacroScripts are stored. */ - private static final String MTSCRIPT_PUBLIC_DIR = "public/"; + public static final String MTSCRIPT_PUBLIC_DIR = "public/"; /** Logger instance for this class. */ private static final Logger logger = LogManager.getLogger(AddOnLibrary.class); diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 1af2a89085..40a7b33726 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -3006,6 +3006,9 @@ library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example # Game Data data.error.cantConvertTo = Can''t convert {0} to {1}. From 0ba585c0b46dececab9db1ebfce4298da2ce537d Mon Sep 17 00:00:00 2001 From: cwisniew Date: Mon, 9 Sep 2024 12:22:29 +0930 Subject: [PATCH 017/146] Add JavaDoc --- .../ui/addon/AddOnLibrariesDialogView.java | 74 +++++++++++- .../ui/addon/AddOnLibrariesTableModel.java | 15 ++- .../client/ui/addon/CreateNewAddonDialog.java | 58 +++++++++- .../addon/ExternalAddOnImportCellEditor.java | 8 ++ .../ExternalAddOnLibrariesTableModel.java | 3 + .../client/ui/addon/creator/NewAddOn.java | 18 +++ .../ui/addon/creator/NewAddOnBuilder.java | 105 +++++++++++++++++- .../ui/addon/creator/NewAddOnCreator.java | 78 +++++++++++-- .../net/rptools/maptool/util/FileUtil.java | 42 +++---- .../rptools/maptool/language/i18n.properties | 1 + 10 files changed, 368 insertions(+), 34 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index a263adfb9d..ac501b8415 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -54,35 +54,88 @@ /** Dialog for managing add-on libraries. */ public class AddOnLibrariesDialogView extends JDialog { - private JPanel contentPane; + /** Removes an add-on library. */ private JButton buttonRemove; + + /** Closes the dialog. */ private JButton buttonClose; + + /** The tabbed pane for the dialog. */ private JTabbedPane tabbedPane; + + /** Table containing the imported add-ons */ private JTable addOnLibraryTable; + + /** The text pane for the add-on description. */ private JTextPane addOnDescriptionTextPane; + + /** Adds an add-on library. */ private JButton buttonAdd; + + /** The name of the selected add-on. */ private JLabel addOnNameLabel; + + /** The version of the selected add-on. */ private JLabel addOnVersionLabel; + + /** The authors of the selected add-on. */ private JLabel addOnAuthorsLabel; + + /** The namespace of the selected add-on. */ private JLabel addOnNamespaceLabel; + + /** The short description of the selected add-on. */ private JLabel addOnShortDescLabel; + + /** The website of the selected add-on. */ private JLabel addOnWebsiteLabel; + + /** The git URL of the selected add-on. */ private JLabel addOnGitUrlLabel; + + /** The license of the selected add-on. */ private JLabel addOnLicenseLabel; + + /** The button for viewing the README file of the selected add-on. */ private JButton viewReadMeFileButton; + + /** The button for viewing the license file of the selected add-on. */ private JButton viewLicenseFileButton; + + /** The button for copying the theme CSS. */ private JButton copyThemeCSS; + + /** The button for copying the stat sheet theme. */ private JButton copyStatSheetThemeButton; + + /** The checkbox for enabling external add-ons. */ private JCheckBox enableExternalAddOnCheckBox; + + /** The table for external add-ons. */ private JTable externalAddonTable; + + /** The button for creating an add-on skeleton. */ private JButton createAddonSkeletonButton; + + /** The text field for the add-on development directory. */ private JTextField directoryTextField; + + /** The button for browsing to select the add-on development directory. */ private JButton browseButton; + + /** Exports the selected add-on. */ private JButton exportAddOnButton; + /** The content pane for the dialog. */ + private JPanel contentPane; + + /** The information for the selected add-on. */ private LibraryInfo selectedAddOn; + /** The model for the external add-on libraries table. */ private final ExternalAddOnLibrariesTableModel externalAddOnLibrariesTableModel; + + /** The model for the add-on libraries table. */ private final AddOnLibrariesTableModel addOnLibrariesTableModel; /** Creates a new instance of the dialog. */ @@ -267,13 +320,22 @@ public void windowClosing(WindowEvent e) { pack(); } - private void refreshLibraries() {} + private void refreshLibraries() { + // TODO: CDW Implement this method + } + /** + * Sets the enabled state of the external add-on controls. + * @param selected the state to set. + */ private void setExternalAddOnControlsEnabled(boolean selected) { externalAddonTable.setEnabled(selected); browseButton.setEnabled(selected); } + /** + * Creates a new add-on skeleton. + */ private void createAddonSkeleton() { var dialog = new CreateNewAddonDialog(); dialog.pack(); @@ -352,6 +414,10 @@ private void addAddOnLibrary() { } } + /** + * Views the license file for the given library. + * @param libInfo the library to view the license file for. + */ private void viewLicenseFile(LibraryInfo libInfo) { Optional lib = new LibraryManager().getLibrary(libInfo.namespace()); lib.ifPresent( @@ -364,6 +430,10 @@ private void viewLicenseFile(LibraryInfo libInfo) { asset -> new ViewAssetDialog(asset, "License", 640, 480).showModal()))); } + /** + * Views the README file for the given library. + * @param libInfo the library to view the README file for. + */ private void viewReadMeFile(LibraryInfo libInfo) { Optional lib = new LibraryManager().getLibrary(libInfo.namespace()); lib.ifPresent( diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java index 4805fc8cd0..2ef73433e3 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java @@ -27,19 +27,32 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * The AddOnLibrariesTableModel class is a table model for displaying add-on libraries in the + * a JTable. + */ public class AddOnLibrariesTableModel extends AbstractTableModel { + + /** The logger. */ private static final Logger log = LogManager.getLogger(AddOnLibrariesDialogController.class); + /** The list of add-on libraries. */ private final List addons = new ArrayList<>(); + /** The AddOnLibrariesTableModel constructor. */ public AddOnLibrariesTableModel() { try { addons.addAll(new LibraryManager().getLibraries(LibraryType.ADD_ON)); } catch (ExecutionException | InterruptedException e) { - log.error("Error displaying add-on libraries", e); + log.error(I18N.getText("library.dialog.error.displayingAddons"), e); } } + /** + * Get the add-on library at the specified row of the table. + * @param row the row. + * @return the add-on library. + */ public LibraryInfo getAddOn(int row) { return addons.get(row); } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java index 71c8cd6902..dd9b766607 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java @@ -40,28 +40,69 @@ import net.rptools.maptool.client.ui.addon.creator.NewAddOnCreator; import net.rptools.maptool.language.I18N; +/** + * Dialog for creating a new Add-On library skeleton. + */ public class CreateNewAddonDialog extends JDialog { + /** The content pane. */ private JPanel contentPane; + + /** OK button which will create the new Add-On. */ private JButton buttonOK; + + /** Cancel button which will close the dialog. */ private JButton buttonCancel; + + /** Text field for the Add-On name. */ private JTextField nameTextField; + + /** Text field for the Add-On version. */ private JTextField versionTextField; + + /** Check box for creating events. */ private JCheckBox eventsCheckBox; + + /** Text pane for the Add-On description. */ private JTextPane descriptionTextPane; + + /** Button for browsing for the directory to create the new add-on in. */ private JButton directoryBrowseButton; + + /** Text field for the Add-On namespace. */ private JTextField namespaceTextField; + + /** Text field for the Git URL. */ private JTextField gitURLTextField; + + /** Text field for the website URL. */ private JTextField websiteTextField; + + /** Text field for the license. */ private JTextField licenseTextField; + + /** Text field for the authors. */ private JTextField authorsTextField; + + /** Text field for the parent directory. */ private JTextField parentDirectoryTextField; + + /** Check box for creating slash commands. */ private JCheckBox slashCommandCheckBox; + + /** Check box for creating MTS properties. */ private JCheckBox mtsPropCheckBox; + + /** Check box for creating UDFs. */ private JCheckBox udfCheckBox; + + /** Text field for the short description. */ private JTextField shortDescTextBox; + + /** Text field for the directory name. */ private JTextField directoryTextField; + /** Creates a new CreateNewAddonDialog. */ public CreateNewAddonDialog() { setContentPane(contentPane); setModal(true); @@ -147,6 +188,9 @@ public void changedUpdate(DocumentEvent e) { validateInputs(); } + /** + * Sets the default values for the new add-on fields. + */ private void setDefaults() { namespaceTextField.setText("net.some-example.addon"); nameTextField.setText("Example Add-On"); @@ -156,6 +200,9 @@ private void setDefaults() { parentDirectoryTextField.setText(AppPreferences.getCreateAddOnParentDir()); } + /** + * Validates the input fields. + */ private void validateInputs() { buttonOK.setEnabled( !namespaceTextField.getText().isEmpty() @@ -166,6 +213,11 @@ private void validateInputs() { && !authorsTextField.getText().isEmpty()); } + /** + * Handles the OK button click. + * This will attempt to create the new add-on skeleton based on the users inputs and then close + * the dialog. + */ private void onOK() { var parentDir = new File(parentDirectoryTextField.getText()); if (!parentDir.exists()) { @@ -203,12 +255,16 @@ private void onOK() { return; } try { - new NewAddOnCreator(newAddon).create(dir.toPath()); + new NewAddOnCreator(newAddon, dir.toPath()).create(); } catch (IOException e) { JOptionPane.showMessageDialog(this, e.getMessage()); } } + /** + * Handles the cancel button click. + * This closes the dialog without attempting to create the new add-on skeleton. + */ private void onCancel() { dispose(); } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java index 8b6773776b..ca06f9b94f 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java @@ -25,12 +25,19 @@ import net.rptools.maptool.model.library.LibraryManager; import net.rptools.maptool.model.library.addon.ExternalLibraryInfo; +/** + * Cell editor for the import button in the external add-on dialog. + */ public class ExternalAddOnImportCellEditor extends AbstractCellEditor implements TableCellEditor, TableCellRenderer { + /** The button that is displayed in the cell. */ private final JButton button = new JButton(); + + /** The external library info for the currently displayed row. */ private ExternalLibraryInfo info; + /** Creates a new cell editor. */ public ExternalAddOnImportCellEditor() { button.addActionListener( e -> { @@ -61,6 +68,7 @@ public Component getTableCellRendererComponent( return getButton(table, value, isSelected, hasFocus, row, column); } + /** Returns the button component for the given cell. */ private Component getButton( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { button.setEnabled(true); diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java index a0417721a5..5c3f4f9814 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java @@ -22,6 +22,9 @@ import net.rptools.maptool.model.library.LibraryManager; import net.rptools.maptool.model.library.addon.ExternalLibraryInfo; +/** + * The ExternalAddOnLibrariesTableModel class is a table model for the external add-on libraries. + */ public class ExternalAddOnLibrariesTableModel extends AbstractTableModel { @Override diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java index 2e08773128..d63f752b8a 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java @@ -16,6 +16,24 @@ import java.util.List; +/** + * Represents a new AddOn to be created. + * @param name The name of the AddOn. + * @param version The version of the AddOn. + * @param namespace The namespace of the AddOn. + * @param gitURL The git URL of the AddOn. + * @param website The website of the AddOn. + * @param license The license of the AddOn. + * @param shortDescription The short description of the AddOn. + * @param description The description of the AddOn. + * @param authors The authors of the AddOn. + * @param createEvents Whether to create example events. + * @param createSlashCommands Whether to create example slash commands. + * @param createMTSProperties Whether to create example MTS properties. + * @param createUDFs Whether to create example UDFs. (not yet implemented). + * @param readme The readme of the AddOn. + * @param licenseText The license text of the AddOn. + */ public record NewAddOn( String name, String version, diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java index dc08b1bdea..8d0b228945 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java @@ -18,90 +18,185 @@ import java.util.List; import net.rptools.maptool.language.I18N; +/** + * Builder for creating a {@link NewAddOn}. + */ public class NewAddOnBuilder { + /** The name of the add-on. */ private String name; + + /** The version of the add-on. */ private String version; + + /** The namespace of the add-on. */ private String namespace; + + /** The git URL of the add-on. */ private String gitURL; + + /** The website of the add-on. */ private String website; + + /** The license of the add-on. */ private String license; + /** The short description of the add-on. */ private String shortDescription; + + /** The description of the add-on. */ private String description; - private List authors = new ArrayList<>(); + /** The authors of the add-on. */ + private final List authors = new ArrayList<>(); + + /** Whether to create events. */ private boolean createEvents; + + /** Whether to create slash commands. */ private boolean createSlashCommands; + + /** Whether to create MTS properties. */ private boolean createMTSProperties; + + /** Whether to create UDFs. (not yet implemented) */ private boolean createUDFs; + /** + * Sets the name of the add-on. + * @param name the name of the add-on + * @return this builder. + */ public NewAddOnBuilder setName(String name) { this.name = name; return this; } + /** + * Sets the version of the add-on. + * @param version the version of the add-on + * @return this builder. + */ public NewAddOnBuilder setVersion(String version) { this.version = version; return this; } + /** + * Sets the namespace of the add-on. + * @param namespace the namespace of the add-on + * @return this builder. + */ public NewAddOnBuilder setNamespace(String namespace) { this.namespace = namespace; return this; } + /** + * Sets the git URL of the add-on. + * @param gitURL the git URL of the add-on + * @return this builder. + */ public NewAddOnBuilder setGitURL(String gitURL) { this.gitURL = gitURL; return this; } + /** + * Sets the website of the add-on. + * @param website the website of the add-on + * @return this builder. + */ public NewAddOnBuilder setWebsite(String website) { this.website = website; return this; } + /** + * Sets the license of the add-on. + * @param license the license of the add-on + * @return this builder. + */ public NewAddOnBuilder setLicense(String license) { this.license = license; return this; } + /** + * Sets the short description of the add-on. + * @param shortDescription the short description of the add-on + * @return this builder. + */ public NewAddOnBuilder setShortDescription(String shortDescription) { this.shortDescription = shortDescription; return this; } + /** + * Sets the description of the add-on. + * @param description the description of the add-on + * @return this builder. + */ public NewAddOnBuilder setDescription(String description) { this.description = description; return this; } + /** + * Sets the authors of the add-on. + * @param authors the authors of the add-on + * @return this builder. + */ public NewAddOnBuilder setAuthors(List authors) { this.authors.clear(); this.authors.addAll(authors); return this; } + /** + * Sets whether to create events. + * @param events whether to create events. + * @return this builder. + */ public NewAddOnBuilder setCreateEvents(boolean events) { this.createEvents = events; return this; } + /** + * Sets whether to create slash commands. + * @param slashCommands whether to create slash commands. + * @return this builder. + */ public NewAddOnBuilder setCreateSlashCommands(boolean slashCommands) { this.createSlashCommands = slashCommands; return this; } + /** + * Sets whether to create MTS properties. + * @param mtsProperties whether to create MTS properties. + * @return this builder. + */ public NewAddOnBuilder setCreateMTSProperties(boolean mtsProperties) { this.createMTSProperties = mtsProperties; return this; } + /** + * Sets whether to create UDFs. + * @param udfs whether to create UDFs. + * @return this builder. + */ public NewAddOnBuilder setCreateUDFs(boolean udfs) { this.createUDFs = udfs; return this; } + /** + * Builds a new {@link NewAddOn} from the current state of the builder. + * @return a new {@link NewAddOn}. + */ public NewAddOn build() { return new NewAddOn( name, @@ -121,10 +216,18 @@ public NewAddOn build() { getLicenseText()); } + /** + * Gets the default README text. + * @return the default README text. + */ private String getReadMe() { return I18N.getText("library.dialog.addon.create.readeMe"); } + /** + * Gets the default license text. + * @return the default license text. + */ private String getLicenseText() { return I18N.getText("library.dialog.addon.create.licenseText"); } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java index 0cad0a230e..21c077ecec 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java @@ -15,32 +15,57 @@ import net.rptools.maptool.model.library.proto.AddonSlashCommandsDto.AddOnSlashCommand; import net.rptools.maptool.model.library.proto.MTScriptPropertiesDto; +/** + * Creates a new add-on directory structure and files. + */ public class NewAddOnCreator { + /** The name of the README file. */ private static final String README_FILE = "README.md"; - private static final String LICENSE_FILE = "license.txt"; + /** The name of the LICENSE file. */ + private static final String LICENSE_FILE = "license.txt"; + /** The add-on to create. */ private final NewAddOn addOn; - private Path addOnPath; - private Path libraryPath; - private Path mtscriptPath; - private Path publicPath; - private Path mtscriptPublicPath; + /** The path to the add-on directory. */ + private final Path addOnPath; - public NewAddOnCreator(NewAddOn newAddOn) { - addOn = newAddOn; - } + /** The path to the library directory. */ + private final Path libraryPath; + + /** The path to the mtscript directory. */ + private final Path mtscriptPath; + /** The path to the public directory. */ + private final Path publicPath; - public void create(Path path) throws IOException { + /** The path to the mtscript public directory. */ + private final Path mtscriptPublicPath; + + /** + * Creates a new add-on creator. + * @param newAddOn the add-on to create. + * @param path the path to the add-on directory which will be created. + * + */ + public NewAddOnCreator(NewAddOn newAddOn, Path path) { + addOn = newAddOn; addOnPath = path; libraryPath = path.resolve(AddOnLibraryImporter.LIBRARY_INFO_FILE); mtscriptPath = libraryPath.resolve(AddOnLibrary.MTSCRIPT_DIR); publicPath = libraryPath.resolve(AddOnLibrary.URL_PUBLIC_DIR); mtscriptPublicPath = mtscriptPath.resolve(AddOnLibrary.MTSCRIPT_PUBLIC_DIR); + } + + + /** + * Creates the add-on directory structure and files. + * @throws IOException if an error occurs creating the directory or files. + */ + public void create() throws IOException { createAddOnDirectories(); createLibraryFile(); @@ -58,6 +83,10 @@ public void create(Path path) throws IOException { createLicenseFile(); } + /** + * Creates the README file. + * @throws IOException if an error occurs creating the file. + */ private void createLicenseFile() throws IOException { try { var licensePath = addOnPath.resolve(LICENSE_FILE); @@ -70,6 +99,10 @@ private void createLicenseFile() throws IOException { } } + /** + * Creates the README file. + * @throws IOException if an error occurs creating the file. + */ private void createReadmeFile() throws IOException { try { var readmePath = addOnPath.resolve(README_FILE); @@ -82,6 +115,10 @@ private void createReadmeFile() throws IOException { } } + /** + * Creates the MTS properties file. + * @throws IOException if an error occurs creating the file. + */ private void createMTSProperties() throws IOException { var builder = MTScriptPropertiesDto.newBuilder(); var propertiesBuilderAutoExec = MTScriptPropertiesDto.Property.newBuilder(); @@ -109,6 +146,10 @@ private void createMTSProperties() throws IOException { // TODO: CDW Create MTS properties macros } + /** + * Creates the slash commands file. + * @throws IOException if an error occurs creating the file. + */ private void createSlashCommands() throws IOException { var builder = AddonSlashCommandsDto.newBuilder(); var slashCommandBuilder = AddOnSlashCommand.newBuilder(); @@ -130,6 +171,10 @@ private void createSlashCommands() throws IOException { // TODO: CDW Create Slash command macro } + /** + * Creates the events file. + * @throws IOException if an error occurs creating the file. + */ private void createEvents() throws IOException { // Add init events var builder = AddOnLibraryEventsDto.newBuilder(); @@ -176,6 +221,10 @@ private void createEvents() throws IOException { } + /** + * Creates the library file. + * @throws IOException if an error occurs creating the file. + */ private void createLibraryFile() throws IOException { var builder = AddOnLibraryDto.newBuilder(); builder.setName(addOn.name()); @@ -207,6 +256,10 @@ private void createLibraryFile() throws IOException { } + /** + * Creates the add-on directories. + * @throws IOException if an error occurs creating the directories. + */ private void createAddOnDirectories() throws IOException { createAddOnDirectory(addOnPath); createAddOnDirectory(libraryPath); @@ -216,6 +269,11 @@ private void createAddOnDirectories() throws IOException { } + /** + * Creates a new directory. + * @param path the path to the directory to create. + * @throws IOException if an error occurs creating the directory.. + */ private void createAddOnDirectory(Path path) throws IOException { if (!path.toFile().mkdirs()) { throw new IOException(I18N.getText("library.dialog.failedToCreateDir", path.toString())); diff --git a/src/main/java/net/rptools/maptool/util/FileUtil.java b/src/main/java/net/rptools/maptool/util/FileUtil.java index 352b017b4d..f582cfcef2 100644 --- a/src/main/java/net/rptools/maptool/util/FileUtil.java +++ b/src/main/java/net/rptools/maptool/util/FileUtil.java @@ -23,7 +23,9 @@ import java.math.RoundingMode; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import net.rptools.maptool.client.AppUtil; @@ -214,25 +216,27 @@ public static File cleanFileName(String fileName, String extension) { * children. */ public static Stream listRecursively(Path dir) throws IOException { - var list = Files.list(dir).collect(Collectors.toSet()); - try { - return Stream.concat( - list.stream(), - list.stream() - .flatMap( - p -> { - if (Files.isDirectory(p)) - try { - return listRecursively(p).map(p::resolve); - } catch (IOException e) { - throw new RuntimeException(e); - } - return null; - }) - .filter(Objects::nonNull)); - } catch (RuntimeException e) { - // This is to bypass an uncaught exception in the above lambda that calls in place. - throw (IOException) e.getCause(); + try (var pathStream = Files.list(dir)) { + var list = pathStream.collect(Collectors.toSet()); + try { + return Stream.concat( + list.stream(), + list.stream() + .flatMap( + p -> { + if (Files.isDirectory(p)) + try { + return listRecursively(p).map(p::resolve); + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; + }) + .filter(Objects::nonNull)); + } catch (RuntimeException e) { + // This is to bypass an uncaught exception in the above lambda that calls in place. + throw (IOException) e.getCause(); + } } } } diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 40a7b33726..56b8555b56 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -3009,6 +3009,7 @@ library.dialog.failedToCreateFile = Failed to create file {0} for add-on. library.dialog.create.exampleSlashCmdDesc = Example Slash Command library.dialog.create.autoExecDesc = Auto Exec in macro link example library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries # Game Data data.error.cantConvertTo = Can''t convert {0} to {1}. From 24ccb0bdccbb0c7bef50b5fdb3a02a71d70c9666 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Thu, 12 Sep 2024 20:51:47 +0930 Subject: [PATCH 018/146] create .mtlib file --- build.gradle | 3 + .../ui/addon/AddOnLibrariesDialogView.form | 18 +-- .../ui/addon/AddOnLibrariesDialogView.java | 79 ++++++++-- .../ui/addon/AddOnLibrariesTableModel.java | 5 +- .../client/ui/addon/CreateNewAddonDialog.java | 26 +--- .../addon/ExternalAddOnImportCellEditor.java | 4 +- .../ExternalAddOnLibrariesTableModel.java | 5 + .../client/ui/addon/creator/MTLibCreator.java | 102 ++++++++++++ .../client/ui/addon/creator/NewAddOn.java | 1 + .../ui/addon/creator/NewAddOnBuilder.java | 20 ++- .../ui/addon/creator/NewAddOnCreator.java | 145 ++++++++++++------ .../addon/ExternalAddOnLibraryManager.java | 22 +++ .../net/rptools/maptool/util/FileUtil.java | 2 - .../rptools/maptool/language/i18n.properties | 6 +- 14 files changed, 344 insertions(+), 94 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/creator/MTLibCreator.java diff --git a/build.gradle b/build.gradle index b3249fb704..6b02779417 100644 --- a/build.gradle +++ b/build.gradle @@ -485,6 +485,9 @@ dependencies { // Built In Add-on Libraries implementation 'com.github.RPTools:maptool-builtin-addons:1.3' + // File watcher library to work around some inconsistencies with java.nio.file.WatchService + implementation 'io.methvin:directory-watcher:0.18.0' + // For advanced dice roller implementation 'com.github.RPTools:advanced-dice-roller:1.0.3' } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form index d19f4f78b8..42ba1f8bfb 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form @@ -299,15 +299,6 @@
- - - - - - - - - @@ -337,6 +328,15 @@ + + + + + + + + + diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index ac501b8415..895f898e7d 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -36,6 +36,7 @@ import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.KeyStroke; +import javax.swing.filechooser.FileNameExtensionFilter; import net.rptools.maptool.client.AppActions.MapPreviewFileChooser; import net.rptools.maptool.client.AppConstants; import net.rptools.maptool.client.AppPreferences; @@ -43,6 +44,7 @@ import net.rptools.maptool.client.swing.SwingUtil; import net.rptools.maptool.client.ui.JLabelHyperLinkListener; import net.rptools.maptool.client.ui.ViewAssetDialog; +import net.rptools.maptool.client.ui.addon.creator.MTLibCreator; import net.rptools.maptool.events.MapToolEventBus; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.library.Library; @@ -123,12 +125,12 @@ public class AddOnLibrariesDialogView extends JDialog { /** The button for browsing to select the add-on development directory. */ private JButton browseButton; - /** Exports the selected add-on. */ - private JButton exportAddOnButton; - /** The content pane for the dialog. */ private JPanel contentPane; + /** Creates a .mtlib (zip) file for the selected add-on. */ + private JButton createMTLibButton; + /** The information for the selected add-on. */ private LibraryInfo selectedAddOn; @@ -138,6 +140,9 @@ public class AddOnLibrariesDialogView extends JDialog { /** The model for the add-on libraries table. */ private final AddOnLibrariesTableModel addOnLibrariesTableModel; + /** The currently selected external add-on. */ + private ExternalLibraryInfo selectedExternalAddon; + /** Creates a new instance of the dialog. */ public AddOnLibrariesDialogView() { setContentPane(contentPane); @@ -161,6 +166,21 @@ public AddOnLibrariesDialogView() { ExternalLibraryInfo.class, new ExternalAddOnImportCellEditor()); externalAddonTable.setDefaultEditor( ExternalLibraryInfo.class, new ExternalAddOnImportCellEditor()); + externalAddonTable + .getSelectionModel() + .addListSelectionListener( + e -> { + int selectedRow = externalAddonTable.getSelectedRow(); + if (selectedRow == -1) { + createMTLibButton.setEnabled(false); + selectedExternalAddon = null; + } else { + createMTLibButton.setEnabled(true); + selectedExternalAddon = + (ExternalLibraryInfo) + externalAddOnLibrariesTableModel.getValueAt(selectedRow, 6); + } + }); buttonRemove.setEnabled(false); addOnLibraryTable @@ -310,6 +330,32 @@ public void windowClosing(WindowEvent e) { } }); + createMTLibButton.addActionListener( + e -> { + if (selectedExternalAddon == null) { + return; + } + + var fileChooser = new JFileChooser(); + fileChooser.setFileFilter( + new FileNameExtensionFilter( + I18N.getText("library.dialog.addon.fileFilter"), "mtlib")); + if (fileChooser.showSaveDialog(MapTool.getFrame()) == JFileChooser.APPROVE_OPTION) { + var outputPath = fileChooser.getSelectedFile().toPath(); + if (!outputPath.toString().endsWith(".mtlib")) { + outputPath = outputPath.resolveSibling(outputPath.getFileName() + ".mtlib"); + } + var creator = + new MTLibCreator( + selectedExternalAddon.backingDirectory(), + outputPath.getParent(), + outputPath.getFileName().toString()); + creator.create(); + } + }); + + createMTLibButton.setEnabled(false); + LibraryManager libraryManager = new LibraryManager(); enableExternalAddOnCheckBox.setSelected(libraryManager.externalLibrariesEnabled()); directoryTextField.setText(AppPreferences.getExternalAddOnLibrariesPath()); @@ -320,12 +366,15 @@ public void windowClosing(WindowEvent e) { pack(); } + /** Refreshes the external add-on libraries table information. */ private void refreshLibraries() { - // TODO: CDW Implement this method + var model = (ExternalAddOnLibrariesTableModel) externalAddonTable.getModel(); + model.refresh(); } /** * Sets the enabled state of the external add-on controls. + * * @param selected the state to set. */ private void setExternalAddOnControlsEnabled(boolean selected) { @@ -333,9 +382,7 @@ private void setExternalAddOnControlsEnabled(boolean selected) { browseButton.setEnabled(selected); } - /** - * Creates a new add-on skeleton. - */ + /** Creates a new add-on skeleton. */ private void createAddonSkeleton() { var dialog = new CreateNewAddonDialog(); dialog.pack(); @@ -416,6 +463,7 @@ private void addAddOnLibrary() { /** * Views the license file for the given library. + * * @param libInfo the library to view the license file for. */ private void viewLicenseFile(LibraryInfo libInfo) { @@ -427,11 +475,18 @@ private void viewLicenseFile(LibraryInfo libInfo) { .thenAccept( a -> a.ifPresent( - asset -> new ViewAssetDialog(asset, "License", 640, 480).showModal()))); + asset -> + new ViewAssetDialog( + asset, + I18N.getText("library.dialog.addon.license"), + 640, + 480) + .showModal()))); } /** * Views the README file for the given library. + * * @param libInfo the library to view the README file for. */ private void viewReadMeFile(LibraryInfo libInfo) { @@ -443,6 +498,12 @@ private void viewReadMeFile(LibraryInfo libInfo) { .thenAccept( a -> a.ifPresent( - asset -> new ViewAssetDialog(asset, "License", 640, 480).showModal()))); + asset -> + new ViewAssetDialog( + asset, + I18N.getText("library.dialog.addon.readme"), + 640, + 480) + .showModal()))); } } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java index 2ef73433e3..579ae81f9c 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java @@ -28,8 +28,8 @@ import org.apache.logging.log4j.Logger; /** - * The AddOnLibrariesTableModel class is a table model for displaying add-on libraries in the - * a JTable. + * The AddOnLibrariesTableModel class is a table model for displaying add-on libraries in the a + * JTable. */ public class AddOnLibrariesTableModel extends AbstractTableModel { @@ -50,6 +50,7 @@ public AddOnLibrariesTableModel() { /** * Get the add-on library at the specified row of the table. + * * @param row the row. * @return the add-on library. */ diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java index dd9b766607..1f05005abe 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java @@ -40,9 +40,7 @@ import net.rptools.maptool.client.ui.addon.creator.NewAddOnCreator; import net.rptools.maptool.language.I18N; -/** - * Dialog for creating a new Add-On library skeleton. - */ +/** Dialog for creating a new Add-On library skeleton. */ public class CreateNewAddonDialog extends JDialog { /** The content pane. */ @@ -188,9 +186,7 @@ public void changedUpdate(DocumentEvent e) { validateInputs(); } - /** - * Sets the default values for the new add-on fields. - */ + /** Sets the default values for the new add-on fields. */ private void setDefaults() { namespaceTextField.setText("net.some-example.addon"); nameTextField.setText("Example Add-On"); @@ -200,9 +196,7 @@ private void setDefaults() { parentDirectoryTextField.setText(AppPreferences.getCreateAddOnParentDir()); } - /** - * Validates the input fields. - */ + /** Validates the input fields. */ private void validateInputs() { buttonOK.setEnabled( !namespaceTextField.getText().isEmpty() @@ -214,9 +208,8 @@ private void validateInputs() { } /** - * Handles the OK button click. - * This will attempt to create the new add-on skeleton based on the users inputs and then close - * the dialog. + * Handles the OK button click. This will attempt to create the new add-on skeleton based on the + * users inputs and then close the dialog. */ private void onOK() { var parentDir = new File(parentDirectoryTextField.getText()); @@ -249,11 +242,6 @@ private void onOK() { .setCreateMTSProperties(mtsPropCheckBox.isSelected()) .build(); dispose(); - if (!dir.mkdirs()) { - JOptionPane.showMessageDialog( - this, I18N.getText("library.dialog.failedToCreateDir", dir.toString())); - return; - } try { new NewAddOnCreator(newAddon, dir.toPath()).create(); } catch (IOException e) { @@ -262,8 +250,8 @@ private void onOK() { } /** - * Handles the cancel button click. - * This closes the dialog without attempting to create the new add-on skeleton. + * Handles the cancel button click. This closes the dialog without attempting to create the new + * add-on skeleton. */ private void onCancel() { dispose(); diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java index ca06f9b94f..e0c9722140 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java @@ -25,9 +25,7 @@ import net.rptools.maptool.model.library.LibraryManager; import net.rptools.maptool.model.library.addon.ExternalLibraryInfo; -/** - * Cell editor for the import button in the external add-on dialog. - */ +/** Cell editor for the import button in the external add-on dialog. */ public class ExternalAddOnImportCellEditor extends AbstractCellEditor implements TableCellEditor, TableCellRenderer { diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java index 5c3f4f9814..a822cd33e6 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java @@ -89,6 +89,11 @@ public String getColumnName(int column) { */ @Subscribe public void handleExternalAddOnsAdded(ExternalAddonsUpdateEvent event) { + refresh(); + } + + /** Refresh the table data. */ + public void refresh() { fireTableDataChanged(); } } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/MTLibCreator.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/MTLibCreator.java new file mode 100644 index 0000000000..267446d9e8 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/addon/creator/MTLibCreator.java @@ -0,0 +1,102 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.ui.addon.creator; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashSet; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import net.rptools.maptool.client.MapTool; + +/** + * This class is responsible for creating a .mtlib file from the contents of a directory. + * + *

The .mtlib file is a zip file that contains all the files in the directory. + * + *

The .mtlib file is used to install the add-on into MapTool. + */ +public class MTLibCreator { + + /** The path to the directory to create the .mtlib file from. */ + private final Path addOnPath; + + /** The output path for the .mtlib file. */ + private final Path outputPath; + + /** The name of the .mtlib file to create. */ + private final String fileName; + + /** + * Create a new instance of the MTLibCreator. + * + * @param addOnPath The path to the directory to create the .mtlib file from. + * @param outputPath The output path for the .mtlib file. + * @param fileName The name of the .mtlib file to create. + */ + public MTLibCreator(Path addOnPath, Path outputPath, String fileName) { + this.addOnPath = addOnPath; + this.outputPath = outputPath; + this.fileName = fileName; + } + + /** Create the .mtlib file. */ + public void create() { + try (var zipOut = new ZipOutputStream(Files.newOutputStream(outputPath.resolve(fileName)))) { + zipOut.setLevel(Deflater.BEST_COMPRESSION); + var fileList = new HashSet(); + try (var pathStream = Files.find(addOnPath, Integer.MAX_VALUE, this::includeFile)) { + pathStream.forEach(fileList::add); + } + for (Path path : fileList) { + zipOut.putNextEntry(new ZipEntry(addOnPath.relativize(path).toString())); + Files.copy(path, zipOut); + } + } catch (Exception e) { + MapTool.showError("library.dialog.addon.errorCreatingMTLib", e); + } + } + + /** + * Determine if a file should be included in the .mtlib file. + * + * @param path The path to the file. + * @param att The attributes of the file. + * @return True if the file should be included, false otherwise. + */ + private boolean includeFile(Path path, BasicFileAttributes att) { + var subPath = addOnPath.relativize(path); + + if (att.isDirectory()) { + return false; + } + + if (subPath.getNameCount() == 0) { + return false; + } + + if (subPath.getName(0).toString().startsWith(".")) { + return false; + } + + if (path.getFileName().toString().toLowerCase().endsWith(".mtlib")) { + return false; + } + + return true; + } +} diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java index d63f752b8a..367195e97c 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java @@ -18,6 +18,7 @@ /** * Represents a new AddOn to be created. + * * @param name The name of the AddOn. * @param version The version of the AddOn. * @param namespace The namespace of the AddOn. diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java index 8d0b228945..4b8257970c 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java @@ -18,9 +18,7 @@ import java.util.List; import net.rptools.maptool.language.I18N; -/** - * Builder for creating a {@link NewAddOn}. - */ +/** Builder for creating a {@link NewAddOn}. */ public class NewAddOnBuilder { /** The name of the add-on. */ @@ -64,6 +62,7 @@ public class NewAddOnBuilder { /** * Sets the name of the add-on. + * * @param name the name of the add-on * @return this builder. */ @@ -74,6 +73,7 @@ public NewAddOnBuilder setName(String name) { /** * Sets the version of the add-on. + * * @param version the version of the add-on * @return this builder. */ @@ -84,6 +84,7 @@ public NewAddOnBuilder setVersion(String version) { /** * Sets the namespace of the add-on. + * * @param namespace the namespace of the add-on * @return this builder. */ @@ -94,6 +95,7 @@ public NewAddOnBuilder setNamespace(String namespace) { /** * Sets the git URL of the add-on. + * * @param gitURL the git URL of the add-on * @return this builder. */ @@ -104,6 +106,7 @@ public NewAddOnBuilder setGitURL(String gitURL) { /** * Sets the website of the add-on. + * * @param website the website of the add-on * @return this builder. */ @@ -114,6 +117,7 @@ public NewAddOnBuilder setWebsite(String website) { /** * Sets the license of the add-on. + * * @param license the license of the add-on * @return this builder. */ @@ -124,6 +128,7 @@ public NewAddOnBuilder setLicense(String license) { /** * Sets the short description of the add-on. + * * @param shortDescription the short description of the add-on * @return this builder. */ @@ -134,6 +139,7 @@ public NewAddOnBuilder setShortDescription(String shortDescription) { /** * Sets the description of the add-on. + * * @param description the description of the add-on * @return this builder. */ @@ -144,6 +150,7 @@ public NewAddOnBuilder setDescription(String description) { /** * Sets the authors of the add-on. + * * @param authors the authors of the add-on * @return this builder. */ @@ -155,6 +162,7 @@ public NewAddOnBuilder setAuthors(List authors) { /** * Sets whether to create events. + * * @param events whether to create events. * @return this builder. */ @@ -165,6 +173,7 @@ public NewAddOnBuilder setCreateEvents(boolean events) { /** * Sets whether to create slash commands. + * * @param slashCommands whether to create slash commands. * @return this builder. */ @@ -175,6 +184,7 @@ public NewAddOnBuilder setCreateSlashCommands(boolean slashCommands) { /** * Sets whether to create MTS properties. + * * @param mtsProperties whether to create MTS properties. * @return this builder. */ @@ -185,6 +195,7 @@ public NewAddOnBuilder setCreateMTSProperties(boolean mtsProperties) { /** * Sets whether to create UDFs. + * * @param udfs whether to create UDFs. * @return this builder. */ @@ -195,6 +206,7 @@ public NewAddOnBuilder setCreateUDFs(boolean udfs) { /** * Builds a new {@link NewAddOn} from the current state of the builder. + * * @return a new {@link NewAddOn}. */ public NewAddOn build() { @@ -218,6 +230,7 @@ public NewAddOn build() { /** * Gets the default README text. + * * @return the default README text. */ private String getReadMe() { @@ -226,6 +239,7 @@ private String getReadMe() { /** * Gets the default license text. + * * @return the default license text. */ private String getLicenseText() { diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java index 21c077ecec..25054c2955 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java @@ -1,10 +1,24 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ package net.rptools.maptool.client.ui.addon.creator; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; import com.google.protobuf.util.JsonFormat; +import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; +import java.io.PrintWriter; import java.nio.file.Path; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.library.addon.AddOnLibrary; @@ -15,9 +29,7 @@ import net.rptools.maptool.model.library.proto.AddonSlashCommandsDto.AddOnSlashCommand; import net.rptools.maptool.model.library.proto.MTScriptPropertiesDto; -/** - * Creates a new add-on directory structure and files. - */ +/** Creates a new add-on directory structure and files. */ public class NewAddOnCreator { /** The name of the README file. */ @@ -26,7 +38,6 @@ public class NewAddOnCreator { /** The name of the LICENSE file. */ private static final String LICENSE_FILE = "license.txt"; - /** The add-on to create. */ private final NewAddOn addOn; @@ -45,24 +56,35 @@ public class NewAddOnCreator { /** The path to the mtscript public directory. */ private final Path mtscriptPublicPath; + private static final String AUTO_EXEC_EXAMPLE_MACRO = "public/auto_exec.mts"; + private static final String NON_AUTO_EXEC_EXAMPLE_MACRO = "public/non_auto_exec.mts"; + private static final String SLASH_COMMAND_EXAMPLE_MACRO = "testSlash.mts"; + private static final String ON_FIRST_INIT_EXAMPLE_MACRO = "onFirstInit.mts"; + private static final String ON_INIT_EXAMPLE_MACRO = "onInit.mts"; + private static final String ON_INITIATIVE_CHANGE_REQUEST_EXAMPLE_MACRO = + "onInitiativeChangeRequest.mts"; + private static final String ON_INITIATIVE_CHANGE_EXAMPLE_MACRO = "onInitiativeChange.mts"; + private static final String ON_TOKEN_MOVE_EXAMPLE_MACRO = "onTokenMove.mts"; + private static final String ON_MULTIPLE_TOKENS_MOVE_EXAMPLE_MACRO = "onMultipleTokensMove.mts"; + /** * Creates a new add-on creator. + * * @param newAddOn the add-on to create. * @param path the path to the add-on directory which will be created. - * */ public NewAddOnCreator(NewAddOn newAddOn, Path path) { addOn = newAddOn; addOnPath = path; - libraryPath = path.resolve(AddOnLibraryImporter.LIBRARY_INFO_FILE); + libraryPath = path.resolve(AddOnLibraryImporter.CONTENT_DIRECTORY); mtscriptPath = libraryPath.resolve(AddOnLibrary.MTSCRIPT_DIR); publicPath = libraryPath.resolve(AddOnLibrary.URL_PUBLIC_DIR); mtscriptPublicPath = mtscriptPath.resolve(AddOnLibrary.MTSCRIPT_PUBLIC_DIR); } - /** * Creates the add-on directory structure and files. + * * @throws IOException if an error occurs creating the directory or files. */ public void create() throws IOException { @@ -85,6 +107,7 @@ public void create() throws IOException { /** * Creates the README file. + * * @throws IOException if an error occurs creating the file. */ private void createLicenseFile() throws IOException { @@ -94,13 +117,13 @@ private void createLicenseFile() throws IOException { writer.write(addOn.licenseText()); writer.close(); } catch (IOException e) { - throw new IOException(I18N.getText("library.dialog.failedToCreateFile", - LICENSE_FILE), e); + throw new IOException(I18N.getText("library.dialog.failedToCreateFile", LICENSE_FILE), e); } } /** * Creates the README file. + * * @throws IOException if an error occurs creating the file. */ private void createReadmeFile() throws IOException { @@ -110,44 +133,49 @@ private void createReadmeFile() throws IOException { writer.write(addOn.readme()); writer.close(); } catch (IOException e) { - throw new IOException(I18N.getText("library.dialog.failedToCreateFile", - README_FILE), e); + throw new IOException(I18N.getText("library.dialog.failedToCreateFile", README_FILE), e); } } /** * Creates the MTS properties file. + * * @throws IOException if an error occurs creating the file. */ private void createMTSProperties() throws IOException { var builder = MTScriptPropertiesDto.newBuilder(); var propertiesBuilderAutoExec = MTScriptPropertiesDto.Property.newBuilder(); - propertiesBuilderAutoExec.setFilename("public/auto_exec.mts"); + propertiesBuilderAutoExec.setFilename(AUTO_EXEC_EXAMPLE_MACRO); propertiesBuilderAutoExec.setAutoExecute(true); propertiesBuilderAutoExec.setDescription(I18N.getText("library.dialog.create.autoExecDesc")); var propertiesBuilderNoAutoExec = MTScriptPropertiesDto.Property.newBuilder(); - propertiesBuilderNoAutoExec.setFilename("public/no_auto_exec.mts"); + propertiesBuilderNoAutoExec.setFilename(NON_AUTO_EXEC_EXAMPLE_MACRO); propertiesBuilderNoAutoExec.setAutoExecute(false); - propertiesBuilderNoAutoExec.setDescription(I18N.getText("library.dialog.create.noAutoExecDesc")); + propertiesBuilderNoAutoExec.setDescription( + I18N.getText("library.dialog.create.noAutoExecDesc")); builder.addProperties(propertiesBuilderAutoExec); builder.addProperties(propertiesBuilderNoAutoExec); try { - var mtsPropertiesPath = mtscriptPath.resolve(AddOnLibraryImporter.MACROSCRIPT_PROPERTY_FILE); + var mtsPropertiesPath = addOnPath.resolve(AddOnLibraryImporter.MACROSCRIPT_PROPERTY_FILE); var writer = new FileWriter(mtsPropertiesPath.toFile()); - writer.write(JsonFormat.printer().includingDefaultValueFields().print(builder)); + writer.write(JsonFormat.printer().print(builder)); writer.close(); } catch (IOException e) { - throw new IOException(I18N.getText("library.dialog.failedToCreateFile", - AddOnLibraryImporter.MACROSCRIPT_PROPERTY_FILE), e); + throw new IOException( + I18N.getText( + "library.dialog.failedToCreateFile", AddOnLibraryImporter.MACROSCRIPT_PROPERTY_FILE), + e); } - // TODO: CDW Create MTS properties macros + writeMacro(mtscriptPath.resolve(AUTO_EXEC_EXAMPLE_MACRO)); + writeMacro(mtscriptPath.resolve(NON_AUTO_EXEC_EXAMPLE_MACRO)); } /** * Creates the slash commands file. + * * @throws IOException if an error occurs creating the file. */ private void createSlashCommands() throws IOException { @@ -155,24 +183,27 @@ private void createSlashCommands() throws IOException { var slashCommandBuilder = AddOnSlashCommand.newBuilder(); slashCommandBuilder.setName("exampleSlash"); slashCommandBuilder.setDescription(I18N.getText("library.dialog.create.exampleSlashCmdDesc")); - slashCommandBuilder.setCommand("testSlash.mts"); + slashCommandBuilder.setCommand(SLASH_COMMAND_EXAMPLE_MACRO); builder.addSlashCommands(slashCommandBuilder); try { - var slashCommandPath = mtscriptPath.resolve(AddOnLibraryImporter.SLASH_COMMAND_FILE); + var slashCommandPath = addOnPath.resolve(AddOnLibraryImporter.SLASH_COMMAND_FILE); var writer = new FileWriter(slashCommandPath.toFile()); - writer.write(JsonFormat.printer().includingDefaultValueFields().print(builder)); + writer.write(JsonFormat.printer().print(builder)); writer.close(); } catch (IOException e) { - throw new IOException(I18N.getText("library.dialog.failedToCreateFile", - AddOnLibraryImporter.SLASH_COMMAND_FILE), e); + throw new IOException( + I18N.getText( + "library.dialog.failedToCreateFile", AddOnLibraryImporter.SLASH_COMMAND_FILE), + e); } - - // TODO: CDW Create Slash command macro + + writeMacro(mtscriptPath.resolve(SLASH_COMMAND_EXAMPLE_MACRO)); } /** * Creates the events file. + * * @throws IOException if an error occurs creating the file. */ private void createEvents() throws IOException { @@ -180,49 +211,56 @@ private void createEvents() throws IOException { var builder = AddOnLibraryEventsDto.newBuilder(); var onFirstInitMTSBuilder = AddOnLibraryEventsDto.Events.newBuilder(); onFirstInitMTSBuilder.setName("onFirstInit"); - onFirstInitMTSBuilder.setMts("onFirstInit.mts"); + onFirstInitMTSBuilder.setMts(ON_FIRST_INIT_EXAMPLE_MACRO); builder.addEvents(onFirstInitMTSBuilder); var onInitMTSBuilder = AddOnLibraryEventsDto.Events.newBuilder(); onInitMTSBuilder.setName("onInit"); - onInitMTSBuilder.setMts("onInit.mts"); + onInitMTSBuilder.setMts(ON_INIT_EXAMPLE_MACRO); builder.addEvents(onInitMTSBuilder); // Add legacy events var legacyEventsBuilder = AddOnLibraryEventsDto.newBuilder(); var onInitiativeChangeRequestBuilder = AddOnLibraryEventsDto.Events.newBuilder(); onInitiativeChangeRequestBuilder.setName("onInitiativeChangeRequest"); - onInitiativeChangeRequestBuilder.setMts("onInitiativeChangeRequest.mts"); + onInitiativeChangeRequestBuilder.setMts(ON_INITIATIVE_CHANGE_REQUEST_EXAMPLE_MACRO); var onInitiativeChangeBuilder = AddOnLibraryEventsDto.Events.newBuilder(); onInitiativeChangeBuilder.setName("onInitiativeChange"); - onInitiativeChangeBuilder.setMts("onInitiativeChange.mts"); + onInitiativeChangeBuilder.setMts(ON_INITIATIVE_CHANGE_EXAMPLE_MACRO); legacyEventsBuilder.addLegacyEvents(onInitiativeChangeBuilder); var onTokenMoveBuilder = AddOnLibraryEventsDto.Events.newBuilder(); onTokenMoveBuilder.setName("onTokenMove"); - onTokenMoveBuilder.setMts("onTokenMove.mts"); + onTokenMoveBuilder.setMts(ON_TOKEN_MOVE_EXAMPLE_MACRO); legacyEventsBuilder.addLegacyEvents(onTokenMoveBuilder); var onMultipleTokensMoveBuilder = AddOnLibraryEventsDto.Events.newBuilder(); onMultipleTokensMoveBuilder.setName("onMultipleTokensMove"); - onMultipleTokensMoveBuilder.setMts("onMultipleTokensMove.mts"); + onMultipleTokensMoveBuilder.setMts(ON_MULTIPLE_TOKENS_MOVE_EXAMPLE_MACRO); legacyEventsBuilder.addLegacyEvents(onMultipleTokensMoveBuilder); // Add events to builder builder.addAllEvents(legacyEventsBuilder.getLegacyEventsList()); try { - var eventsPath = mtscriptPath.resolve(AddOnLibraryImporter.EVENT_PROPERTY_FILE); + var eventsPath = addOnPath.resolve(AddOnLibraryImporter.EVENT_PROPERTY_FILE); var writer = new FileWriter(eventsPath.toFile()); - writer.write(JsonFormat.printer().includingDefaultValueFields().print(builder)); + writer.write(JsonFormat.printer().print(builder)); writer.close(); } catch (IOException e) { - throw new IOException(I18N.getText("library.dialog.failedToCreateFile", - AddOnLibraryImporter.EVENT_PROPERTY_FILE), e); + throw new IOException( + I18N.getText( + "library.dialog.failedToCreateFile", AddOnLibraryImporter.EVENT_PROPERTY_FILE), + e); } - // TODO: CDW Create event macros - + writeMacro(mtscriptPath.resolve(ON_FIRST_INIT_EXAMPLE_MACRO)); + writeMacro(mtscriptPath.resolve(ON_INIT_EXAMPLE_MACRO)); + writeMacro(mtscriptPath.resolve(ON_INITIATIVE_CHANGE_REQUEST_EXAMPLE_MACRO)); + writeMacro(mtscriptPath.resolve(ON_INITIATIVE_CHANGE_EXAMPLE_MACRO)); + writeMacro(mtscriptPath.resolve(ON_TOKEN_MOVE_EXAMPLE_MACRO)); + writeMacro(mtscriptPath.resolve(ON_MULTIPLE_TOKENS_MOVE_EXAMPLE_MACRO)); } /** * Creates the library file. + * * @throws IOException if an error occurs creating the file. */ private void createLibraryFile() throws IOException { @@ -245,19 +283,20 @@ private void createLibraryFile() throws IOException { builder.setLicenseFile(LICENSE_FILE); builder.setReadMeFile(README_FILE); try { - var libraryPath = addOnPath.resolve(AddOnLibraryImporter.LIBRARY_INFO_FILE); - var writer = new FileWriter(libraryPath.toFile()); + var libraryInfoPath = addOnPath.resolve(AddOnLibraryImporter.LIBRARY_INFO_FILE); + var writer = new FileWriter(libraryInfoPath.toFile()); writer.write(JsonFormat.printer().includingDefaultValueFields().print(builder)); writer.close(); } catch (IOException e) { - throw new IOException(I18N.getText("library.dialog.failedToCreateFile", - AddOnLibraryImporter.LIBRARY_INFO_FILE), e); + throw new IOException( + I18N.getText("library.dialog.failedToCreateFile", AddOnLibraryImporter.LIBRARY_INFO_FILE), + e); } - } /** * Creates the add-on directories. + * * @throws IOException if an error occurs creating the directories. */ private void createAddOnDirectories() throws IOException { @@ -268,9 +307,9 @@ private void createAddOnDirectories() throws IOException { createAddOnDirectory(publicPath); } - /** * Creates a new directory. + * * @param path the path to the directory to create. * @throws IOException if an error occurs creating the directory.. */ @@ -279,4 +318,18 @@ private void createAddOnDirectory(Path path) throws IOException { throw new IOException(I18N.getText("library.dialog.failedToCreateDir", path.toString())); } } + + /** + * Write an example macro to the given path. + * + * @param path the path to write the macro to (including filename) + */ + private void writeMacro(Path path) throws FileNotFoundException { + var filename = path.getFileName().toString(); + var comment = I18N.getText("library.dialog.addon.create.mtsComment", filename); + try (var writer = new PrintWriter(path.toFile())) { + writer.println(""); + writer.println("[h: broadcast('" + filename + "')] "); + } + } } diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java index 297b55e9da..93804d6bb6 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java @@ -372,6 +372,24 @@ public void importLibrary(String namespace) throws IOException { } } + /** + * Checks to see if the specified path is an ignored sub-path when watching the external add-on + * + * @param path the path to check. + * @return {@code true} if the path is an ignored sub-path. + */ + private boolean isIgnoredSubPath(Path path) { + if (path.startsWith(".git")) { + return true; + } + + if (path.getFileName().toString().toLowerCase().endsWith(".mtlib")) { + return true; + } + + return false; + } + /** * Stops the add-on library with the specified namespace from being available to MapTool. * @@ -392,6 +410,10 @@ private DirectoryWatcher createDirectoryWatcher() throws IOException { if (!path.startsWith(externalLibraryPath)) { return; } + var subPath = externalLibraryPath.relativize(path); + if (isIgnoredSubPath(subPath)) { + return; + } path = externalLibraryPath.resolve(path.getName(basePathNameCount)); switch (event.eventType()) { case CREATE -> registerExternalAddOnLibrary(path); diff --git a/src/main/java/net/rptools/maptool/util/FileUtil.java b/src/main/java/net/rptools/maptool/util/FileUtil.java index f582cfcef2..a08240193b 100644 --- a/src/main/java/net/rptools/maptool/util/FileUtil.java +++ b/src/main/java/net/rptools/maptool/util/FileUtil.java @@ -23,9 +23,7 @@ import java.math.RoundingMode; import java.nio.file.Files; import java.nio.file.Path; -import java.util.HashSet; import java.util.Objects; -import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import net.rptools.maptool.client.AppUtil; diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 56b8555b56..71677dd3e4 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2968,6 +2968,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2986,7 +2987,7 @@ library.dialog.addon.imported = Imported library.dialog.addon.updated = Updated library.dialog.addon.refresh = Refresh library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.export = Export Add-On +library.dialog.addon.createMTLib = Create .mtlib file library.dialog.directory.label = Add On Directory library.dialog.parentdir.label = Parent Directory library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format @@ -3010,6 +3011,9 @@ library.dialog.create.exampleSlashCmdDesc = Example Slash Command library.dialog.create.autoExecDesc = Auto Exec in macro link example library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can''t convert {0} to {1}. From 6051f9f50f3f58013b86e11f29d866046cd0a114 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 26 Sep 2024 17:51:27 -0700 Subject: [PATCH 019/146] Fix translation of "Clear GM Auras Only" menu item --- .../net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java b/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java index 6cb4f9f415..06c6f6d7fc 100644 --- a/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java +++ b/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java @@ -729,7 +729,7 @@ public void actionPerformed(ActionEvent e) { public class ClearGMAurasOnlyAction extends AbstractAction { public ClearGMAurasOnlyAction() { - super("token.popup.menu.auras.clearGM"); + super(I18N.getText("token.popup.menu.auras.clearGM")); } public void actionPerformed(ActionEvent e) { From 7da81bd54e5f0c8a962f81f9ca99a345421fe2b9 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 26 Sep 2024 18:12:54 -0700 Subject: [PATCH 020/146] Add clear lights menu items only once, and always add them if possible --- .../client/ui/AbstractTokenPopupMenu.java | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java b/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java index 6cb4f9f415..d7ed970207 100644 --- a/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java +++ b/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java @@ -120,24 +120,33 @@ public void actionPerformed(ActionEvent e) { protected JMenu createLightSourceMenu() { JMenu menu = new JMenu(I18N.getText("panel.MapExplorer.View.LIGHT_SOURCES")); - if (tokenUnderMouse.hasLightSources()) { + boolean hasAnyLights = false; + boolean hasLights = false; + boolean hasAuras = false; + boolean hasGmAuras = false; + boolean hasOwnerOnlyAuras = false; + + for (GUID tokenGUID : selectedTokenSet) { + Token token = renderer.getZone().getToken(tokenGUID); + hasAnyLights |= token.hasLightSources(); + hasLights |= token.hasLightSourceType(LightSource.Type.NORMAL); + hasAuras |= token.hasLightSourceType(LightSource.Type.AURA); + hasGmAuras |= token.hasGMAuras(); + hasOwnerOnlyAuras |= token.hasOwnerOnlyAuras(); + } + if (hasAnyLights) { menu.add(new ClearLightAction()); - - ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); - for (GUID tokenGUID : selectedTokenSet) { - Token token = renderer.getZone().getToken(tokenGUID); - if (token.hasLightSourceType(LightSource.Type.NORMAL)) { - menu.add(new ClearLightsOnlyAction()); - } - if (token.hasLightSourceType(LightSource.Type.AURA)) { - menu.add(new ClearAurasOnlyAction()); - } - if (token.hasGMAuras()) { - menu.add(new ClearGMAurasOnlyAction()); - } - if (token.hasOwnerOnlyAuras()) { - menu.add(new ClearOwnerAurasOnlyAction()); - } + if (hasLights) { + menu.add(new ClearLightsOnlyAction()); + } + if (hasAuras) { + menu.add(new ClearAurasOnlyAction()); + } + if (hasGmAuras) { + menu.add(new ClearGMAurasOnlyAction()); + } + if (hasOwnerOnlyAuras) { + menu.add(new ClearOwnerAurasOnlyAction()); } menu.addSeparator(); } From 0e5d9ff1fa107268ddac18a08c23d862a28d01f9 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Sun, 19 Nov 2023 01:42:42 -0800 Subject: [PATCH 021/146] Avoid failure when touching up asset names Instead of `cp`, using `mv` has some nicer behaviour on Windows and Mac OS, while also being decent on Linux too. Cherry pick of 4e86d6871 --- .github/workflows/publish.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 93b7d3b007..846c63fd0a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -68,11 +68,11 @@ jobs: # | |/ |/ // // / / // /_/ // /_/ /| |/ |/ /(__ ) # |__/|__//_//_/ /_/ \__,_/ \____/ |__/|__//____/ # - - name: Copy Windows Release Files + - name: Rename Windows Release Files if: matrix.os == 'windows-latest' run: | - cp releases/MapTool*.exe releases/MapTool-${{ github.event.release.tag_name }}.exe - cp releases/MapTool*.msi releases/MapTool-${{ github.event.release.tag_name }}.msi + mv releases/MapTool*.exe releases/MapTool-${{ github.event.release.tag_name }}.exe + mv releases/MapTool*.msi releases/MapTool-${{ github.event.release.tag_name }}.msi continue-on-error: true - name: Upload Windows EXE Release Asset id: upload-release-asset-exe @@ -113,12 +113,12 @@ jobs: # / /___ / // / / // /_/ /_> < # /_____//_//_/ /_/ \__,_//_/|_| # - - name: Copy Linux Release Files + - name: Rename Linux Release Files if: matrix.os == 'ubuntu-latest' run: | - cp releases/maptool*.x86_64.rpm releases/maptool-${{ github.event.release.tag_name }}.x86_64.rpm - cp releases/maptool*_amd64.deb releases/maptool_${{ github.event.release.tag_name }}_amd64.deb - cp package/archlinux/maptool/maptool-*-x86_64.pkg.tar.zst releases/maptool-${{ github.event.release.tag_name }}-x86_64.pkg.tar.zst + mv -n releases/maptool*.x86_64.rpm releases/maptool-${{ github.event.release.tag_name }}.x86_64.rpm + mv -n releases/maptool*_amd64.deb releases/maptool_${{ github.event.release.tag_name }}_amd64.deb + mv -n package/archlinux/maptool/maptool-*-x86_64.pkg.tar.zst releases/maptool-${{ github.event.release.tag_name }}-x86_64.pkg.tar.zst continue-on-error: true - name: Upload Linux RPM Release Asset id: upload-release-asset-rpm @@ -159,11 +159,11 @@ jobs: # / / / / / // /_/ // /__ / /_/ /___/ / # /_/ /_/ /_/ \__,_/ \___/ \____//____/ # - - name: Copy Mac OS Release Files + - name: Rename Mac OS Release Files if: matrix.os == 'macOS-13' run: | - cp releases/MapTool*.dmg releases/MapTool-${{ github.event.release.tag_name }}.dmg - cp releases/MapTool*.pkg releases/MapTool-${{ github.event.release.tag_name }}.pkg + mv releases/MapTool*.dmg releases/MapTool-${{ github.event.release.tag_name }}.dmg + mv releases/MapTool*.pkg releases/MapTool-${{ github.event.release.tag_name }}.pkg continue-on-error: true - name: Upload Mac DMG Release Asset id: upload-release-asset-dmg From 4564cb46e1dad1345145f157fad6f77cea7ed198 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Wed, 29 Nov 2023 15:58:09 -0800 Subject: [PATCH 022/146] Mostly undo PRs #4259 and #4285 HTML5 event listeners are now registered as before. However, to avoid peering issues in JavaFX <22, new listeners are created each time the page is unloaded. Cherry pick of 776e07d03 --- .../ui/htmlframe/HTMLWebViewManager.java | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLWebViewManager.java b/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLWebViewManager.java index 942e473cda..837c3c620d 100644 --- a/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLWebViewManager.java +++ b/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLWebViewManager.java @@ -59,6 +59,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.w3c.dom.*; +import org.w3c.dom.events.EventListener; import org.w3c.dom.events.EventTarget; import org.w3c.dom.html.*; @@ -91,6 +92,10 @@ public class HTMLWebViewManager { /** The bridge from Javascript to Java. */ private final JavaBridge bridge; + private EventListener listenerA = this::fixHref; + + private EventListener listenerSubmit = this::getDataAndSubmit; + /** Represents a bridge from Javascript to Java. */ public class JavaBridge { @@ -159,13 +164,50 @@ public void handleAddedNode(Object object) { if (addedNode instanceof EventTarget) { EventTarget target = (EventTarget) addedNode; if (addedNode instanceof HTMLAnchorElement || addedNode instanceof HTMLAreaElement) { - target.addEventListener("click", HTMLWebViewManager.this::fixHref, true); + target.addEventListener("click", listenerA, true); } else if (target instanceof HTMLFormElement) { - target.addEventListener("submit", HTMLWebViewManager.this::getDataAndSubmit, true); + target.addEventListener("submit", listenerSubmit, true); } else if (target instanceof HTMLInputElement || target instanceof HTMLButtonElement) { - target.addEventListener("click", HTMLWebViewManager.this::getDataAndSubmit, true); + target.addEventListener("click", listenerSubmit, true); } } + + // Add listeners to the node's descendant as they don't trigger mutation observer. + NodeList nodeList; + + // Add event handlers for hyperlinks. + nodeList = addedNode.getElementsByTagName("a"); + for (int i = 0; i < nodeList.getLength(); i++) { + EventTarget node = (EventTarget) nodeList.item(i); + node.addEventListener("click", listenerA, true); + } + + // Add event handlers for hyperlinks for maps. + nodeList = addedNode.getElementsByTagName("area"); + for (int i = 0; i < nodeList.getLength(); i++) { + EventTarget node = (EventTarget) nodeList.item(i); + node.addEventListener("click", listenerA, true); + } + + // Set the "submit" handler to get the data on submission not based on buttons + nodeList = addedNode.getElementsByTagName("form"); + for (int i = 0; i < nodeList.getLength(); i++) { + EventTarget target = (EventTarget) nodeList.item(i); + target.addEventListener("submit", listenerSubmit, true); + } + + // Set the "submit" handler to get the data on submission based on input + nodeList = addedNode.getElementsByTagName("input"); + for (int i = 0; i < nodeList.getLength(); i++) { + EventTarget target = (EventTarget) nodeList.item(i); + target.addEventListener("click", listenerSubmit, true); + } + // Set the "submit" handler to get the data on submission based on button + nodeList = addedNode.getElementsByTagName("button"); + for (int i = 0; i < nodeList.getLength(); i++) { + EventTarget target = (EventTarget) nodeList.item(i); + target.addEventListener("click", listenerSubmit, true); + } } } } @@ -269,6 +311,10 @@ public void flush() { // Delete cookies java.net.CookieHandler.setDefault(new java.net.CookieManager()); + // This may look pointless, but we need new objects on JFX <22 to avoid peering issues. + listenerA = this::fixHref; + listenerSubmit = this::getDataAndSubmit; + isFlushed = true; } From ae4cce909ac6e733efbc5be1084c783e23e5a8b2 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Fri, 27 Sep 2024 14:32:12 -0700 Subject: [PATCH 023/146] Fix NPE in Set Facing tool We now handle the case where a token does not have a facing. In that case, the tool updates the other clients to also remove the facing. --- .../maptool/client/tool/FacingTool.java | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/tool/FacingTool.java b/src/main/java/net/rptools/maptool/client/tool/FacingTool.java index fc2564bd16..a922e6c6af 100644 --- a/src/main/java/net/rptools/maptool/client/tool/FacingTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/FacingTool.java @@ -127,15 +127,6 @@ public void mouseMoved(MouseEvent e) { } token.setFacing(degrees); - // Old Logic - // if (renderer.getZone().hasFog() - // && ((AppPreferences.getAutoRevealVisionOnGMMovement() && - // MapTool.getPlayer().isGM())) - // || MapTool.getServerPolicy().isAutoRevealOnMovement()) { - // visibleArea = renderer.getZoneView().getVisibleArea(token); - // remoteSelected.add(token.getId()); - // renderer.getZone().exposeArea(visibleArea, token); - // } boolean revealFog = false; if (renderer.getZone().hasFog()) { if (ownerReveal && token.isOwner(name)) revealFog = true; @@ -168,8 +159,14 @@ public void mousePressed(MouseEvent e) { if (token == null) { continue; } - // Send the facing to other players - MapTool.serverCommand().updateTokenProperty(token, Token.Update.setFacing, token.getFacing()); + + // Send the facing (or lack thereof) to other players + if (!token.hasFacing()) { + MapTool.serverCommand().updateTokenProperty(token, Token.Update.removeFacing); + } else { + MapTool.serverCommand() + .updateTokenProperty(token, Token.Update.setFacing, token.getFacing()); + } } // Go back to the pointer tool resetTool(); From d8987b19e9c4cb3b81043d4cb355886ec9ca3d20 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Fri, 27 Sep 2024 14:20:15 -0700 Subject: [PATCH 024/146] Token no longer has a nullable facing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A token still may not have a facing, but this must now be determined by `Token.hasFacing()`. `Token.getFacing()` will never return `null` but will provide the default of -90°, which removes the case work that several callers had to consistently perform. In order to remove a token's facing, `Token.removeFacing()` can now be used as `Token.setFacing()` no longer accepts `null`. Also remove some unused and redundant code in related places. --- .../functions/TokenPropertyFunctions.java | 2 +- .../maptool/client/tool/DefaultTool.java | 59 +------------------ .../maptool/client/tool/FacingTool.java | 2 +- .../maptool/client/tool/PointerTool.java | 7 +-- .../maptool/client/tool/StampTool.java | 5 +- .../client/ui/AbstractTokenPopupMenu.java | 2 +- .../client/ui/zone/renderer/ZoneRenderer.java | 14 ++--- .../java/net/rptools/maptool/model/Grid.java | 7 --- .../rptools/maptool/model/IsometricGrid.java | 6 -- .../java/net/rptools/maptool/model/Token.java | 35 ++++------- 10 files changed, 28 insertions(+), 111 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenPropertyFunctions.java b/src/main/java/net/rptools/maptool/client/functions/TokenPropertyFunctions.java index aa22f9322f..23ac082b60 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenPropertyFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenPropertyFunctions.java @@ -642,7 +642,7 @@ public Object childEvaluate( if (functionName.equalsIgnoreCase("getTokenFacing")) { FunctionUtil.checkNumberParam(functionName, parameters, 0, 2); Token token = FunctionUtil.getTokenFromParam(resolver, functionName, parameters, 0, 1); - if (token.getFacing() == null) { + if (!token.hasFacing()) { return ""; // XXX Should be -1 instead of a string? } return BigDecimal.valueOf(token.getFacing()); diff --git a/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java b/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java index 5a6f32f012..f35a44b143 100644 --- a/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java @@ -16,7 +16,6 @@ import java.awt.dnd.DragSource; import java.awt.event.*; -import java.awt.geom.AffineTransform; import java.util.Map; import java.util.Set; import javax.swing.*; @@ -322,62 +321,10 @@ public void mouseWheelMoved(MouseWheelEvent e) { if (!AppUtil.playerOwns(token)) { continue; } - Integer facing = token.getFacing(); - if (facing == null) { - facing = -90; // natural alignment - } - if (SwingUtil.isControlDown(e)) { - // Modify on the fly the rotation point - if (e.isAltDown()) { - int x = token.getX(); - int y = token.getY(); - int w = token.getWidth(); - int h = token.getHeight(); - - double xc = x + w / 2; - double yc = y + h / 2; - - facing += e.getWheelRotation() > 0 ? 5 : -5; - token.setFacing(facing); - int a = token.getFacingInDegrees(); - double r = Math.toRadians(a); - - System.out.println("Angle: " + a); - System.out.println("Origin x,y: " + x + ", " + y); - System.out.println("Origin bounds: " + token.getBounds(renderer.getZone())); - // System.out.println("Anchor x,y: " + token.getAnchor().x + ", " + - // token.getAnchor().y); - - // x = (int) ((x + w) - w * Math.cos(r)); - // y = (int) (y - w * Math.sin(r)); - - // double x1 = (x - xc) * Math.cos(r) - (y - yc) * Math.sin(r) + xc; - // double y1 = (y - yc) * Math.cos(r) + (x - xc) * Math.sin(r) + yc; - - // x = (int) (x * Math.cos(r) - y * Math.sin(r)); - // y = (int) (y * Math.cos(r) + x * Math.sin(r)); - AffineTransform at = new AffineTransform(); - at.translate(x, y); - at.rotate(r, x + w, y); - - x = (int) at.getTranslateX(); - y = (int) at.getTranslateY(); - - // token.setX(x); - // token.setY(y); - // renderer.flush(token); - // MapTool.serverCommand().putToken(getZone().getId(), token); - - // token.setX(0); - // token.setY(0); - - System.out.println("New x,y: " + x + ", " + y); - System.out.println("New bounds: " + token.getBounds(renderer.getZone()).toString()); - - } else { - facing += e.getWheelRotation() > 0 ? 5 : -5; - } + int facing = token.getFacing(); + if (SwingUtil.isControlDown(e)) { + facing += e.getWheelRotation() > 0 ? 5 : -5; } else { int[] facingArray = getZone().getGrid().getFacingAngles(); int facingIndex = TokenUtil.getIndexNearestTo(facingArray, facing); diff --git a/src/main/java/net/rptools/maptool/client/tool/FacingTool.java b/src/main/java/net/rptools/maptool/client/tool/FacingTool.java index a922e6c6af..c82da0eb12 100644 --- a/src/main/java/net/rptools/maptool/client/tool/FacingTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/FacingTool.java @@ -72,7 +72,7 @@ public void actionPerformed(ActionEvent e) { if (token == null) { continue; } - token.setFacing(null); + token.removeFacing(); renderer.flush(token); } // Go back to the pointer tool 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 2649bdb65e..986b7f5265 100644 --- a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java @@ -1092,11 +1092,8 @@ private void handleKeyRotate(int direction, boolean freeRotate) { if (!AppUtil.playerOwns(token)) { continue; } - Integer facing = token.getFacing(); - // TODO: this should really be a per grid setting - if (facing == null) { - facing = -90; // natural alignment - } + + int facing = token.getFacing(); if (freeRotate) { facing += direction * 5; } else { diff --git a/src/main/java/net/rptools/maptool/client/tool/StampTool.java b/src/main/java/net/rptools/maptool/client/tool/StampTool.java index 187b0f6e77..1826b03966 100644 --- a/src/main/java/net/rptools/maptool/client/tool/StampTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/StampTool.java @@ -40,7 +40,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Objects; import java.util.Set; import javax.annotation.Nullable; import javax.swing.AbstractAction; @@ -1046,7 +1045,7 @@ public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { if (token.hasFacing() && token.getShape() == Token.TokenShape.TOP_DOWN) { // untested when anchor != (0,0) // rotate the resize image with the stamp. - double theta = Math.toRadians(-token.getFacing() - 90); + double theta = Math.toRadians(token.getFacingInDegrees()); double anchorX = -scaledWidth / 2 + resizeImg.getWidth() - (token.getAnchor().x * scale); double anchorY = @@ -1303,7 +1302,7 @@ public TokenResizeOp( // theta is the rotation angle clockwise from the positive x-axis to compensate for the +ve // y-axis pointing downwards in zone space and an unrotated token has facing of -90. // theta == 0 => token has default rotation. - int theta = -Objects.requireNonNullElse(tokenBeingResized.getFacing(), -90) - 90; + int theta = tokenBeingResized.getFacingInDegrees(); double radians = Math.toRadians(theta); this.down = new Vector2(-Math.sin(radians), Math.cos(radians)); this.right = new Vector2(Math.cos(radians), Math.sin(radians)); diff --git a/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java b/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java index 38a098f25a..76fb3170a4 100644 --- a/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java +++ b/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java @@ -693,7 +693,7 @@ public void actionPerformed(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); - token.setFacing(null); + token.removeFacing(); MapTool.serverCommand().putToken(renderer.getZone().getId(), token); } renderer.repaint(); diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java index 64ca8bba35..939e47e7c5 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java @@ -1496,7 +1496,7 @@ protected void showBlockedMoves(Graphics2D g, PlayerView view, Set if (token.hasFacing() && token.getShape() == Token.TokenShape.TOP_DOWN) { at.rotate( - Math.toRadians(-token.getFacing() - 90), + Math.toRadians(token.getFacingInDegrees()), scaledWidth / 2 - token.getAnchor().x * scale - offsetx, scaledHeight / 2 - token.getAnchor().y * scale @@ -2191,7 +2191,7 @@ protected void renderTokens( double sy = scaledHeight / 2 + y - (token.getAnchor().y * scale); tokenBounds.transform( AffineTransform.getRotateInstance( - Math.toRadians(-token.getFacing() - 90), sx, sy)); // facing + Math.toRadians(token.getFacingInDegrees()), sx, sy)); // facing // defaults // to // down, @@ -2394,7 +2394,7 @@ protected void renderTokens( // (token.getAnchor().y * scale) - offsety); at.rotate( - Math.toRadians(-token.getFacing() - 90), + Math.toRadians(token.getFacingInDegrees()), location.scaledWidth / 2 - (token.getAnchor().x * scale) - offsetx, location.scaledHeight / 2 - (token.getAnchor().y * scale) - offsety); // facing defaults to down, or -90 degrees @@ -2502,9 +2502,7 @@ protected void renderTokens( Token.TokenShape tokenType = token.getShape(); switch (tokenType) { case FIGURE: - if (token.getHasImageTable() - && token.hasFacing() - && AppPreferences.getForceFacingArrow() == false) { + if (token.getHasImageTable() && AppPreferences.getForceFacingArrow() == false) { break; } Shape arrow = getFigureFacingArrow(token.getFacing(), footprintBounds.width / 2); @@ -2719,7 +2717,7 @@ protected void renderTokens( // Rotated clippedG.translate(sp.x, sp.y); clippedG.rotate( - Math.toRadians(-token.getFacing() - 90), + Math.toRadians(token.getFacingInDegrees()), width / 2 - (token.getAnchor().x * scale), height / 2 - (token.getAnchor().y * scale)); // facing defaults to down, or -90 // degrees @@ -3376,7 +3374,7 @@ private BufferedImage getTokenImage(Token token) { MapTool.getCampaign().getLookupTableMap().get(token.getImageTableName()); if (lookupTable != null) { try { - LookupEntry result = lookupTable.getLookup(token.getFacing().toString()); + LookupEntry result = lookupTable.getLookup(Integer.toString(token.getFacing())); if (result != null) { image = ImageManager.getImage(result.getImageId(), this); } diff --git a/src/main/java/net/rptools/maptool/model/Grid.java b/src/main/java/net/rptools/maptool/model/Grid.java index bf11e27508..d90a0af7e6 100644 --- a/src/main/java/net/rptools/maptool/model/Grid.java +++ b/src/main/java/net/rptools/maptool/model/Grid.java @@ -406,9 +406,6 @@ public void setSize(int size) { -visionRange, -visionRange, visionRange * 2, visionRange * 2)); break; case BEAM: - if (token.getFacing() == null) { - token.setFacing(0); - } // Make at least 1 pixel on each side, so it's at least visible at 100% zoom. var pixelWidth = Math.max(2, width * getSize() / zone.getUnitsPerCell()); Shape lineShape = new Rectangle2D.Double(0, -pixelWidth / 2, visionRange, pixelWidth); @@ -421,10 +418,6 @@ public void setSize(int size) { .createTransformedShape(visibleShape)); break; case CONE: - if (token.getFacing() == null) { - token.setFacing(0); - } - Arc2D cone = new Arc2D.Double( -visionRange, diff --git a/src/main/java/net/rptools/maptool/model/IsometricGrid.java b/src/main/java/net/rptools/maptool/model/IsometricGrid.java index b79c26bfc3..b2c5805a31 100644 --- a/src/main/java/net/rptools/maptool/model/IsometricGrid.java +++ b/src/main/java/net/rptools/maptool/model/IsometricGrid.java @@ -351,9 +351,6 @@ public Area getShapedArea( visibleArea = new Area(new Polygon(x, y, 4)); break; case BEAM: - if (token.getFacing() == null) { - token.setFacing(0); - } var pixelWidth = Math.max(2, width * getSize() / getZone().getUnitsPerCell()); Shape visibleShape = new Rectangle2D.Double(0, -pixelWidth / 2, visionRange, pixelWidth); @@ -374,9 +371,6 @@ public Area getShapedArea( break; case CONE: - if (token.getFacing() == null) { - token.setFacing(0); - } // Rotate the vision range by 45 degrees for isometric view visionRange = (float) Math.sin(Math.toRadians(45)) * visionRange; // Get the cone, use degreesFromIso to convert the facing from isometric to plan diff --git a/src/main/java/net/rptools/maptool/model/Token.java b/src/main/java/net/rptools/maptool/model/Token.java index 4595b36f15..0252943ddd 100644 --- a/src/main/java/net/rptools/maptool/model/Token.java +++ b/src/main/java/net/rptools/maptool/model/Token.java @@ -848,14 +848,18 @@ public boolean hasFacing() { return facing != null; } - public void setFacing(Integer facing) { - while (facing != null && (facing > 180 || facing < -179)) { + public void setFacing(int facing) { + while (facing > 180 || facing < -179) { facing += facing > 180 ? -360 : 0; facing += facing < -179 ? 360 : 0; } this.facing = facing; } + public void removeFacing() { + this.facing = null; + } + /** * Facing is in the map space where 0 degrees is along the X axis to the right and proceeding CCW * for positive angles. @@ -864,8 +868,9 @@ public void setFacing(Integer facing) { * * @return null or angle in degrees */ - public Integer getFacing() { - return facing; + public int getFacing() { + // -90° is natural alignment. TODO This should really be a per grid setting + return facing == null ? -90 : facing; } /** @@ -874,24 +879,8 @@ public Integer getFacing() { * * @return angle in degrees */ - public Integer getFacingInDegrees() { - if (facing == null) { - return 0; - } else { - return -(facing + 90); - } - } - - public Integer getFacingInRealDegrees() { - if (facing == null) { - return 270; - } - - if (facing >= 0) { - return facing; - } else { - return facing + 360; - } + public int getFacingInDegrees() { + return -getFacing() - 90; } public boolean getHasSight() { @@ -2728,7 +2717,7 @@ public void updateProperty(Zone zone, Update update, List setFacing(parameters.get(0).getIntValue()); break; case removeFacing: - setFacing(null); + removeFacing(); break; case clearAllOwners: clearAllOwners(); From 280da950847338f04106c8eaa7fec71f644aa926 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Sun, 29 Sep 2024 14:29:46 +0800 Subject: [PATCH 025/146] New translations i18n.properties (English, Australia) --- .../net/rptools/maptool/language/i18n_en_AU.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties b/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties index 13ecdf233a..161c10b90f 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties @@ -523,7 +523,7 @@ Preferences.developer.showAiDebugging.tooltip = When enabled, adds labels c Preferences.developer.ignoreGridShapeCache.label = Ignore grid shape cache Preferences.developer.ignoreGridShapeCache.tooltip = When enabled, the grid's shape is recalculated every time it is needed. Preferences.developer.debugTokenDragging.label = Enable token drag debugging -Preferences.developer.debugTokenDragging.tooltip = When enabled, highlights key points used during token drags, such as anchor points. +Preferences.developer.debugTokenDragging.tooltip = When enabled, highlights key points used during token drags such as anchor points. Preferences.developer.info.developerOptionsInUsePost = If this is not intended, go to {0} > {1} > {2} tab and disable the options there. Preferences.tab.interactions = Interactions Preferences.label.maps.fow = New maps have Fog of War From e1fa7ab8464d459a37f92204a95868047d3c7cd8 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Sun, 29 Sep 2024 14:29:47 +0800 Subject: [PATCH 026/146] New translations i18n.properties (English, United Kingdom) --- .../net/rptools/maptool/language/i18n_en_GB.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties b/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties index 3bb8f996a0..74702102fa 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties @@ -523,7 +523,7 @@ Preferences.developer.showAiDebugging.tooltip = When enabled, adds labels c Preferences.developer.ignoreGridShapeCache.label = Ignore grid shape cache Preferences.developer.ignoreGridShapeCache.tooltip = When enabled, the grid's shape is recalculated every time it is needed. Preferences.developer.debugTokenDragging.label = Enable token drag debugging -Preferences.developer.debugTokenDragging.tooltip = When enabled, highlights key points used during token drags, such as anchor points. +Preferences.developer.debugTokenDragging.tooltip = When enabled, highlights key points used during token drags such as anchor points. Preferences.developer.info.developerOptionsInUsePost = If this is not intended, go to {0} > {1} > {2} tab and disable the options there. Preferences.tab.interactions = Interactions Preferences.label.maps.fow = New maps have Fog of War From 38e46a6dc24a79abb563e44c7bc218725e2798f3 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Mon, 30 Sep 2024 01:11:13 +0800 Subject: [PATCH 027/146] New translations i18n.properties (German) --- .../net/rptools/maptool/language/i18n_de_DE.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties b/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties index ae31fa2c44..fbc7d3fb33 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties @@ -522,8 +522,8 @@ Preferences.developer.showAiDebugging.label = KI Debugging aktivieren Preferences.developer.showAiDebugging.tooltip = Wenn aktiviert, werden Labels hinzugefügt, die die f-, g- und h-Kosten enthalten, die von A* bei der Pfadsuche berechnet werden, sowie die von VBL blockierten Bewegungen. Preferences.developer.ignoreGridShapeCache.label = Rasterform-Cache ignorieren Preferences.developer.ignoreGridShapeCache.tooltip = Wenn aktiviert, wird die Rasterform, wenn sie benötigt wird, erneut berechnet. -Preferences.developer.debugTokenDragging.label = Enable token drag debugging -Preferences.developer.debugTokenDragging.tooltip = When enabled, highlights key points used during token drags, such as anchor points. +Preferences.developer.debugTokenDragging.label = Aktiviere Token-Drag-Debugging +Preferences.developer.debugTokenDragging.tooltip = Wenn aktiv werden einzelne Punkte des Token-Drags, wie z.B. Ankerpunkte, hervorgehoben. Preferences.developer.info.developerOptionsInUsePost = Wenn dies nicht beabsichtigt ist, gehen Sie auf den Tab {0} > {1} > {2} und deaktivieren dort die Optionen. Preferences.tab.interactions = Interaktionen Preferences.label.maps.fow = Neue Karten benutzen Kriegsnebel From 35819513fab103277a96cb6d17096e193a90139b Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Tue, 1 Oct 2024 14:48:56 +0800 Subject: [PATCH 028/146] Fixed check in ButtonGroup drag event drop to exit where there is no change in group for the same panel. --- .../client/ui/macrobuttons/buttongroups/ButtonGroup.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/buttongroups/ButtonGroup.java b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/buttongroups/ButtonGroup.java index 713ac6626b..3ad3e1b653 100644 --- a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/buttongroups/ButtonGroup.java +++ b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/buttongroups/ButtonGroup.java @@ -128,8 +128,8 @@ public void drop(DropTargetDropEvent event) { MacroButtonProperties oldMacroProps = new MacroButtonProperties(tempProperties); // stops players from moving macros into/from the Campaign/GM panels - // debounce first, ignore moves that change nothing - if (tempProperties.getGroup().equals(getMacroGroup())) { + // debounce first, ignore moves to the same group in the same panel + if (tempProperties.getGroup().equals(getMacroGroup()) && data.panelClass.equals(panelClass)) { event.dropComplete(false); } else if (!MapTool.getPlayer().isGM() && (panelClass.equals("CampaignPanel") From 701effe27f7d52b6207eacd59f1ed611a0c5f535 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Sat, 28 Sep 2024 01:03:36 -0700 Subject: [PATCH 029/146] Change Grid.getFacing() to not be based on static state Instead, the edge facing and vertex facing preference need to be provided by the caller, and the `Grid` will dynamically decide its response. There is no more static state that needs to be kept up-to-date - state which previously was shared between instances despite being treated like instance state. --- .../client/functions/MapFunctions.java | 4 +- .../maptool/client/tool/DefaultTool.java | 6 +- .../maptool/client/tool/FacingTool.java | 6 +- .../maptool/client/tool/PointerTool.java | 6 +- .../MapPropertiesDialog.java | 16 +--- .../preferencesdialog/PreferencesDialog.java | 22 ------ .../java/net/rptools/maptool/model/Grid.java | 10 +-- .../rptools/maptool/model/GridFactory.java | 12 +-- .../rptools/maptool/model/GridlessGrid.java | 7 +- .../net/rptools/maptool/model/HexGrid.java | 4 - .../maptool/model/HexGridHorizontal.java | 56 ++------------ .../maptool/model/HexGridVertical.java | 44 ++--------- .../rptools/maptool/model/IsometricGrid.java | 73 +++---------------- .../net/rptools/maptool/model/SquareGrid.java | 38 +++------- .../rptools/maptool/model/ZoneFactory.java | 6 +- 15 files changed, 65 insertions(+), 245 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/MapFunctions.java b/src/main/java/net/rptools/maptool/client/functions/MapFunctions.java index 1575a63630..a448e822f0 100644 --- a/src/main/java/net/rptools/maptool/client/functions/MapFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/MapFunctions.java @@ -230,9 +230,7 @@ public Object childEvaluate( gridConfig.has("type") ? gridConfig.getAsJsonPrimitive("type").getAsString() : AppPreferences.getDefaultGridType(); - final var grid = - GridFactory.createGrid( - gridType, AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); + final var grid = GridFactory.createGrid(gridType); final var gridColor = gridConfig.has("color") diff --git a/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java b/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java index 5a6f32f012..e5f1c4b654 100644 --- a/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.Set; import javax.swing.*; +import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.client.AppState; import net.rptools.maptool.client.AppUtil; import net.rptools.maptool.client.MapTool; @@ -379,7 +380,10 @@ public void mouseWheelMoved(MouseWheelEvent e) { facing += e.getWheelRotation() > 0 ? 5 : -5; } } else { - int[] facingArray = getZone().getGrid().getFacingAngles(); + int[] facingArray = + getZone() + .getGrid() + .getFacingAngles(AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); int facingIndex = TokenUtil.getIndexNearestTo(facingArray, facing); facingIndex += e.getWheelRotation() > 0 ? 1 : -1; diff --git a/src/main/java/net/rptools/maptool/client/tool/FacingTool.java b/src/main/java/net/rptools/maptool/client/tool/FacingTool.java index fc2564bd16..bffcb3eb75 100644 --- a/src/main/java/net/rptools/maptool/client/tool/FacingTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/FacingTool.java @@ -101,7 +101,11 @@ public void mouseMoved(MouseEvent e) { int degrees = (int) Math.toDegrees(angle); if (!SwingUtil.isControlDown(e)) { - int[] facingAngles = renderer.getZone().getGrid().getFacingAngles(); + int[] facingAngles = + renderer + .getZone() + .getGrid() + .getFacingAngles(AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); degrees = facingAngles[TokenUtil.getIndexNearestTo(facingAngles, degrees)]; } Area visibleArea = null; 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 2649bdb65e..3d108958a2 100644 --- a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java @@ -1100,7 +1100,11 @@ private void handleKeyRotate(int direction, boolean freeRotate) { if (freeRotate) { facing += direction * 5; } else { - int[] facingArray = renderer.getZone().getGrid().getFacingAngles(); + int[] facingArray = + renderer + .getZone() + .getGrid() + .getFacingAngles(AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); int facingIndex = TokenUtil.getIndexNearestTo(facingArray, facing); facingIndex += direction; diff --git a/src/main/java/net/rptools/maptool/client/ui/mappropertiesdialog/MapPropertiesDialog.java b/src/main/java/net/rptools/maptool/client/ui/mappropertiesdialog/MapPropertiesDialog.java index 5050df31ce..b67c2d70da 100644 --- a/src/main/java/net/rptools/maptool/client/ui/mappropertiesdialog/MapPropertiesDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/mappropertiesdialog/MapPropertiesDialog.java @@ -559,24 +559,16 @@ public int getZoneDistancePerCell() { private Grid createZoneGrid() { Grid grid = null; if (getHexHorizontalRadio().isSelected()) { - grid = - GridFactory.createGrid( - GridFactory.HEX_HORI, AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); + grid = GridFactory.createGrid(GridFactory.HEX_HORI); } if (getHexVerticalRadio().isSelected()) { - grid = - GridFactory.createGrid( - GridFactory.HEX_VERT, AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); + grid = GridFactory.createGrid(GridFactory.HEX_VERT); } if (getSquareRadio().isSelected()) { - grid = - GridFactory.createGrid( - GridFactory.SQUARE, AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); + grid = GridFactory.createGrid(GridFactory.SQUARE); } if (getIsometricRadio().isSelected()) { - grid = - GridFactory.createGrid( - GridFactory.ISOMETRIC, AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); + grid = GridFactory.createGrid(GridFactory.ISOMETRIC); } if (getNoGridRadio().isSelected()) { grid = GridFactory.createGrid(GridFactory.NONE); diff --git a/src/main/java/net/rptools/maptool/client/ui/preferencesdialog/PreferencesDialog.java b/src/main/java/net/rptools/maptool/client/ui/preferencesdialog/PreferencesDialog.java index b0ddb15b15..456649edfc 100644 --- a/src/main/java/net/rptools/maptool/client/ui/preferencesdialog/PreferencesDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/preferencesdialog/PreferencesDialog.java @@ -29,7 +29,6 @@ import java.nio.file.Path; import java.text.ParseException; import java.util.Arrays; -import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; @@ -55,7 +54,6 @@ import net.rptools.maptool.client.walker.WalkerMetric; import net.rptools.maptool.events.MapToolEventBus; import net.rptools.maptool.language.I18N; -import net.rptools.maptool.model.Grid; import net.rptools.maptool.model.GridFactory; import net.rptools.maptool.model.Token; import net.rptools.maptool.model.Zone; @@ -824,12 +822,10 @@ public PreferencesDialog() { facingFaceEdges.addActionListener( e -> { AppPreferences.setFaceEdge(facingFaceEdges.isSelected()); - updateFacings(); }); facingFaceVertices.addActionListener( e -> { AppPreferences.setFaceVertex(facingFaceVertices.isSelected()); - updateFacings(); }); toolTipInlineRolls.addActionListener( @@ -1526,24 +1522,6 @@ public void setVisible(boolean b) { super.setVisible(b); } - /** - * Used by the ActionListeners of the facing checkboxes to update the facings for all of the - * current zones. Redundant to go through all zones because all zones using the same grid type - * share facings but it doesn't hurt anything and avoids having to track what grid types are being - * used. - */ - private void updateFacings() { - // List zlist = MapTool.getServer().getCampaign().getZones(); // generated NPE - // http://forums.rptools.net/viewtopic.php?f=3&t=17334 - List zlist = MapTool.getCampaign().getZones(); - boolean faceEdges = AppPreferences.getFaceEdge(); - boolean faceVertices = AppPreferences.getFaceVertex(); - for (Zone z : zlist) { - Grid g = z.getGrid(); - g.setFacings(faceEdges, faceVertices); - } - } - /** * Initializes and sets the initial state of various user preferences in the application. This * method is called during the initialization process. diff --git a/src/main/java/net/rptools/maptool/model/Grid.java b/src/main/java/net/rptools/maptool/model/Grid.java index bf11e27508..db91d21349 100644 --- a/src/main/java/net/rptools/maptool/model/Grid.java +++ b/src/main/java/net/rptools/maptool/model/Grid.java @@ -112,7 +112,7 @@ public void drawCoordinatesOverlay(Graphics2D g, ZoneRenderer renderer) { } /** - * Set the facing options for tokens/objects on a grid. Each grid type can providing facings to + * Get the facing options for tokens/objects on a grid. Each grid type can providing facings to * the edges, the vertices, both, or neither. * *

If both are false, tokens on that grid will not be able to rotate with the mouse and @@ -121,13 +121,7 @@ public void drawCoordinatesOverlay(Graphics2D g, ZoneRenderer renderer) { * @param faceEdges - Tokens can face edges. * @param faceVertices - Tokens can face vertices. */ - public void setFacings(boolean faceEdges, boolean faceVertices) { - // Handle it in the individual grid types - } - - public int[] getFacingAngles() { - return null; - } + public abstract int[] getFacingAngles(boolean faceEdges, boolean faceVertices); /** * Return the Point (double precision) for pixel center of Cell diff --git a/src/main/java/net/rptools/maptool/model/GridFactory.java b/src/main/java/net/rptools/maptool/model/GridFactory.java index cf599455c1..f10b660b26 100644 --- a/src/main/java/net/rptools/maptool/model/GridFactory.java +++ b/src/main/java/net/rptools/maptool/model/GridFactory.java @@ -29,21 +29,17 @@ public class GridFactory { public static final String NONE = "None"; public static Grid createGrid(String type) { - return createGrid(type, true, false); - } - - public static Grid createGrid(String type, boolean faceEdges, boolean faceVertices) { if (isHexVertical(type)) { - return new HexGridVertical(faceEdges, faceVertices); + return new HexGridVertical(); } if (isHexHorizontal(type)) { - return new HexGridHorizontal(faceEdges, faceVertices); + return new HexGridHorizontal(); } if (isSquare(type)) { - return new SquareGrid(faceEdges, faceVertices); + return new SquareGrid(); } if (isIsometric(type)) { - return new IsometricGrid(faceEdges, faceVertices); + return new IsometricGrid(); } if (isNone(type)) { return new GridlessGrid(); diff --git a/src/main/java/net/rptools/maptool/model/GridlessGrid.java b/src/main/java/net/rptools/maptool/model/GridlessGrid.java index aa79e7ff0e..a4b71399ed 100644 --- a/src/main/java/net/rptools/maptool/model/GridlessGrid.java +++ b/src/main/java/net/rptools/maptool/model/GridlessGrid.java @@ -57,9 +57,8 @@ public boolean isCoordinatesSupported() { return false; } }; - // @formatter:on - private static final int[] FACING_ANGLES = new int[] {-135, -90, -45, 0, 45, 90, 135, 180}; + // @formatter:on @Override public List getFootprints() { @@ -74,8 +73,8 @@ public List getFootprints() { } @Override - public int[] getFacingAngles() { - return FACING_ANGLES; + public int[] getFacingAngles(boolean faceEdges, boolean faceVertices) { + return new int[] {-135, -90, -45, 0, 45, 90, 135, 180}; } @Override diff --git a/src/main/java/net/rptools/maptool/model/HexGrid.java b/src/main/java/net/rptools/maptool/model/HexGrid.java index 49824820ea..c29381a6cc 100644 --- a/src/main/java/net/rptools/maptool/model/HexGrid.java +++ b/src/main/java/net/rptools/maptool/model/HexGrid.java @@ -105,10 +105,6 @@ public Point2D.Double getCenterOffset() { */ protected transient double edgeLength; - public HexGrid() { - super(); - } - @Override public boolean isHex() { return true; diff --git a/src/main/java/net/rptools/maptool/model/HexGridHorizontal.java b/src/main/java/net/rptools/maptool/model/HexGridHorizontal.java index 26911c5ca3..5079593096 100644 --- a/src/main/java/net/rptools/maptool/model/HexGridHorizontal.java +++ b/src/main/java/net/rptools/maptool/model/HexGridHorizontal.java @@ -29,7 +29,6 @@ import java.util.concurrent.ConcurrentHashMap; import javax.swing.Action; import javax.swing.KeyStroke; -import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.tool.PointerTool; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; @@ -49,39 +48,15 @@ * @formatter:on */ public class HexGridHorizontal extends HexGrid { - - private static final int[] ALL_ANGLES = - new int[] {-150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150, 180}; private static final OffsetTranslator OFFSET_TRANSLATOR = (originPoint, offsetPoint) -> { if (Math.abs(originPoint.y) % 2 == 1 && Math.abs(offsetPoint.y) % 2 == 0) { offsetPoint.x++; } }; - /* - * Facings are set when a new map is created with a particular grid and these facings affect all maps with the same grid. Other maps with different grids will remain the same. - * - * Facings are set when maps are loaded to the current preferences. - */ - private static int[] - FACING_ANGLES; // = new int[] {-150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150, 180}; private static List footprintList; private static Map gridShapeCache = new ConcurrentHashMap<>(); - public HexGridHorizontal() { - super(); - if (FACING_ANGLES == null) { - boolean faceEdges = AppPreferences.getFaceEdge(); - boolean faceVertices = AppPreferences.getFaceVertex(); - setFacings(faceEdges, faceVertices); - } - } - - public HexGridHorizontal(boolean faceEdges, boolean faceVertices) { - super(); - setFacings(faceEdges, faceVertices); - } - @Override public boolean isHexHorizontal() { return true; @@ -92,22 +67,17 @@ protected synchronized Map getGridShapeCache() { return gridShapeCache; } - /** - * Set available facings based on the passed parameters. - * - * @param faceEdges - Tokens can face cell faces if true. - * @param faceVertices - Tokens can face cell vertices if true. - */ @Override - public void setFacings(boolean faceEdges, boolean faceVertices) { + public int[] getFacingAngles(boolean faceEdges, boolean faceVertices) { + // TODO Distorted hexes surely require distorted facing angles. if (faceEdges && faceVertices) { - FACING_ANGLES = ALL_ANGLES; - } else if (!faceEdges && faceVertices) { - FACING_ANGLES = new int[] {-150, -90, -30, 30, 90, 150}; - } else if (faceEdges && !faceVertices) { - FACING_ANGLES = new int[] {-120, -60, 0, 60, 120, 180}; + return new int[] {-150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150, 180}; + } else if (faceVertices) { + return new int[] {-150, -90, -30, 30, 90, 150}; + } else if (faceEdges) { + return new int[] {-120, -60, 0, 60, 120, 180}; } else { - FACING_ANGLES = new int[] {90}; + return new int[] {90}; } } @@ -128,16 +98,6 @@ public double cellDistance(CellPoint cellA, CellPoint cellB, WalkerMetric wmetri } } - @Override - public int[] getFacingAngles() { - if (FACING_ANGLES == null) { - boolean faceEdges = AppPreferences.getFaceEdge(); - boolean faceVertices = AppPreferences.getFaceVertex(); - setFacings(faceEdges, faceVertices); - } - return FACING_ANGLES; - } - /* * For a horizontal hex grid we want the following layout: * @formatter:off diff --git a/src/main/java/net/rptools/maptool/model/HexGridVertical.java b/src/main/java/net/rptools/maptool/model/HexGridVertical.java index a1f6dbfed5..4370477b23 100644 --- a/src/main/java/net/rptools/maptool/model/HexGridVertical.java +++ b/src/main/java/net/rptools/maptool/model/HexGridVertical.java @@ -28,7 +28,6 @@ import java.util.concurrent.ConcurrentHashMap; import javax.swing.Action; import javax.swing.KeyStroke; -import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.tool.PointerTool; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; @@ -50,34 +49,15 @@ * @formatter:on */ public class HexGridVertical extends HexGrid { - - private static final int[] ALL_ANGLES = - new int[] {-150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150, 180}; private static final OffsetTranslator OFFSET_TRANSLATOR = (originPoint, offsetPoint) -> { if (Math.abs(originPoint.x) % 2 == 1 && Math.abs(offsetPoint.x) % 2 == 0) { offsetPoint.y++; } }; - private static int[] - FACING_ANGLES; // = new int[] {-150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150, 180}; private static List footprintList; private static Map gridShapeCache = new ConcurrentHashMap<>(); - public HexGridVertical() { - super(); - if (FACING_ANGLES == null) { - boolean faceEdges = AppPreferences.getFaceEdge(); - boolean faceVertices = AppPreferences.getFaceVertex(); - setFacings(faceEdges, faceVertices); - } - } - - public HexGridVertical(boolean faceEdges, boolean faceVertices) { - super(); - setFacings(faceEdges, faceVertices); - } - @Override protected synchronized Map getGridShapeCache() { return gridShapeCache; @@ -101,26 +81,16 @@ public double cellDistance(CellPoint cellA, CellPoint cellB, WalkerMetric wmetri } @Override - public void setFacings(boolean faceEdges, boolean faceVertices) { + public int[] getFacingAngles(boolean faceEdges, boolean faceVertices) { if (faceEdges && faceVertices) { - FACING_ANGLES = ALL_ANGLES; - } else if (!faceEdges && faceVertices) { - FACING_ANGLES = new int[] {-120, -60, 0, 60, 120, 180}; - } else if (faceEdges && !faceVertices) { - FACING_ANGLES = new int[] {-150, -90, -30, 30, 90, 150}; + return new int[] {-150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150, 180}; + } else if (faceVertices) { + return new int[] {-120, -60, 0, 60, 120, 180}; + } else if (faceEdges) { + return new int[] {-150, -90, -30, 30, 90, 150}; } else { - FACING_ANGLES = new int[] {90}; - } - } - - @Override - public int[] getFacingAngles() { - if (FACING_ANGLES == null) { - boolean faceEdges = AppPreferences.getFaceEdge(); - boolean faceVertices = AppPreferences.getFaceVertex(); - setFacings(faceEdges, faceVertices); + return new int[] {90}; } - return FACING_ANGLES; } /* diff --git a/src/main/java/net/rptools/maptool/model/IsometricGrid.java b/src/main/java/net/rptools/maptool/model/IsometricGrid.java index b79c26bfc3..c051caf3c8 100644 --- a/src/main/java/net/rptools/maptool/model/IsometricGrid.java +++ b/src/main/java/net/rptools/maptool/model/IsometricGrid.java @@ -39,32 +39,10 @@ import net.rptools.maptool.util.GraphicsUtil; public class IsometricGrid extends Grid { - /** - * An attempt at an isometric style map grid where each cell is a diamond with the sides angled at - * approx 30 degrees. However rather than being true isometric, each cell is twice as wide as - * high. This makes converting images significantly easier for end-users. - */ - private static final int ISO_ANGLE = 27; - - private static final int[] ALL_ANGLES = new int[] {-135, -90, -45, 0, 45, 90, 135, 180}; - private static int[] FACING_ANGLES; private static List footprintList; private static BufferedImage pathHighlight = RessourceManager.getImage(Images.GRID_BORDER_ISOMETRIC); - public IsometricGrid() { - super(); - if (FACING_ANGLES == null) { - boolean faceEdges = AppPreferences.getFaceEdge(); - boolean faceVertices = AppPreferences.getFaceVertex(); - setFacings(faceEdges, faceVertices); - } - } - - public IsometricGrid(boolean faceEdges, boolean faceVertices) { - setFacings(faceEdges, faceVertices); - } - public boolean isIsometric() { return true; } @@ -102,32 +80,6 @@ public double getCellHeightHalf() { return getSize() / 2; } - public static double degreesFromIso(double facing) { - /** - * Given a facing from an isometric map turn it into plan map equivalent i.e. 27 degree converts - * to 45 degree - */ - double newFacing = facing; - if (Math.cos(facing) != 0) { - double v1 = Math.sin(Math.toRadians(newFacing)) * 2; - double v2 = Math.cos(Math.toRadians(newFacing)); - double v3 = Math.toDegrees(Math.atan(v1 / v2)); - if (facing > 90 || facing < -90) v3 = 180 + v3; - newFacing = Math.floor(v3); - } - return newFacing; - } - - public static double degreesToIso(double facing) { - /** - * Given a facing from a plan map turn it into isometric map equivalent i.e 45 degree converts - * to 30 degree - */ - double iso = Math.asin((Math.sin(facing) / 2) / Math.cos(facing)); - System.out.println("in=" + facing + " out=" + iso); - return iso; - } - @Override public BufferedImage getCellHighlight() { return pathHighlight; @@ -139,8 +91,16 @@ public Dimension getCellOffset() { } @Override - public int[] getFacingAngles() { - return FACING_ANGLES; + public int[] getFacingAngles(boolean faceEdges, boolean faceVertices) { + if (faceEdges && faceVertices) { + return new int[] {-135, -90, -45, 0, 45, 90, 135, 180}; + } else if (faceVertices) { + return new int[] {-90, 0, 90, 180}; + } else if (faceEdges) { + return new int[] {-135, -45, 45, 135}; + } else { + return new int[] {90}; + } } @Override @@ -297,19 +257,6 @@ public void uninstallMovementKeys(Map actionMap) { } } - @Override - public void setFacings(boolean faceEdges, boolean faceVertices) { - if (faceEdges && faceVertices) { - FACING_ANGLES = ALL_ANGLES; - } else if (!faceEdges && faceVertices) { - FACING_ANGLES = new int[] {-90, 0, 90, 180}; - } else if (faceEdges && !faceVertices) { - FACING_ANGLES = new int[] {-135, -45, 45, 135}; - } else { - FACING_ANGLES = new int[] {90}; - } - } - @Override public Area getShapedArea( ShapeType shape, diff --git a/src/main/java/net/rptools/maptool/model/SquareGrid.java b/src/main/java/net/rptools/maptool/model/SquareGrid.java index f0e3650b92..db80dbd8b1 100644 --- a/src/main/java/net/rptools/maptool/model/SquareGrid.java +++ b/src/main/java/net/rptools/maptool/model/SquareGrid.java @@ -78,29 +78,14 @@ public boolean isCoordinatesSupported() { return true; } }; - // @formatter:on - - private static final int[] ALL_ANGLES = new int[] {-135, -90, -45, 0, 45, 90, 135, 180}; - private static int[] FACING_ANGLES; - public SquareGrid() { - super(); - if (FACING_ANGLES == null) { - boolean faceEdges = AppPreferences.getFaceEdge(); - boolean faceVertices = AppPreferences.getFaceVertex(); - setFacings(faceEdges, faceVertices); - } - } + // @formatter:on @Override public Point2D.Double getCenterOffset() { return new Point2D.Double(getCellWidth() / 2, getCellHeight() / 2); } - public SquareGrid(boolean faceEdges, boolean faceVertices) { - setFacings(faceEdges, faceVertices); - } - @Override public void installMovementKeys(PointerTool callback, Map actionMap) { if (movementKeys == null) { @@ -151,15 +136,17 @@ protected void fillDto(GridDto.Builder dto) { } @Override - public void setFacings(boolean faceEdges, boolean faceVertices) { + public int[] getFacingAngles(boolean faceEdges, boolean faceVertices) { if (faceEdges && faceVertices) { - FACING_ANGLES = ALL_ANGLES; - } else if (!faceEdges && faceVertices) { - FACING_ANGLES = new int[] {-135, -45, 45, 135}; - } else if (faceEdges && !faceVertices) { - FACING_ANGLES = new int[] {-90, 0, 90, 180}; + return new int[] {-135, -90, -45, 0, 45, 90, 135, 180}; + } else if (faceVertices) { + // && !faceEdges + return new int[] {-135, -45, 45, 135}; + } else if (faceEdges) { + // && !faceVertices + return new int[] {-90, 0, 90, 180}; } else { - FACING_ANGLES = new int[] {90}; + return new int[] {90}; } } @@ -269,11 +256,6 @@ public double getCellWidth() { return getSize(); } - @Override - public int[] getFacingAngles() { - return FACING_ANGLES; - } - @Override public Point2D.Double getCellCenter(CellPoint cell) { // square have their xy at their top left diff --git a/src/main/java/net/rptools/maptool/model/ZoneFactory.java b/src/main/java/net/rptools/maptool/model/ZoneFactory.java index c4bc0b8554..e28514099a 100644 --- a/src/main/java/net/rptools/maptool/model/ZoneFactory.java +++ b/src/main/java/net/rptools/maptool/model/ZoneFactory.java @@ -63,11 +63,7 @@ public static Zone createZone() { zone.setTokenVisionDistance(AppPreferences.getDefaultVisionDistance()); zone.setVisionType(AppPreferences.getDefaultVisionType()); - zone.setGrid( - GridFactory.createGrid( - AppPreferences.getDefaultGridType(), - AppPreferences.getFaceEdge(), - AppPreferences.getFaceVertex())); + zone.setGrid(GridFactory.createGrid(AppPreferences.getDefaultGridType())); zone.setGridColor(AppPreferences.getDefaultGridColor().getRGB()); zone.getGrid().setSize(AppPreferences.getDefaultGridSize()); zone.getGrid().setOffset(0, 0); From abf7fdd006f101013b79aab70eae73dbb12c353f Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Sat, 28 Sep 2024 00:40:05 -0700 Subject: [PATCH 030/146] Clean up static state held in grid implementations - Avoid uninitialized state where reasonable. - Only remaining case is the footprint list which is loaded lazily. - `final`ize fields where possible. - Remove unused alternative cell highlight fields and associated assets --- .../rptools/maptool/client/ui/theme/Images.java | 1 - .../client/ui/theme/RessourceManager.java | 1 - .../java/net/rptools/maptool/model/Grid.java | 2 +- .../java/net/rptools/maptool/model/HexGrid.java | 3 ++- .../rptools/maptool/model/HexGridHorizontal.java | 2 +- .../rptools/maptool/model/HexGridVertical.java | 2 +- .../net/rptools/maptool/model/IsometricGrid.java | 2 +- .../net/rptools/maptool/model/SquareGrid.java | 2 -- .../maptool/client/image/grid-square-orage.png | Bin 2583 -> 0 bytes .../maptool/client/image/grid-square-red.png | Bin 1920 -> 0 bytes 10 files changed, 6 insertions(+), 9 deletions(-) delete mode 100644 src/main/resources/net/rptools/maptool/client/image/grid-square-orage.png delete mode 100644 src/main/resources/net/rptools/maptool/client/image/grid-square-red.png diff --git a/src/main/java/net/rptools/maptool/client/ui/theme/Images.java b/src/main/java/net/rptools/maptool/client/ui/theme/Images.java index 8f708e51fe..13e67bbfc0 100644 --- a/src/main/java/net/rptools/maptool/client/ui/theme/Images.java +++ b/src/main/java/net/rptools/maptool/client/ui/theme/Images.java @@ -28,7 +28,6 @@ public enum Images { GRID_BORDER_HEX, GRID_BORDER_ISOMETRIC, GRID_BORDER_SQUARE, - GRID_BORDER_SQUARE_RED, HEROLABS_PORTRAIT, HEROLABS_TOKEN, LIGHT_SOURCE, diff --git a/src/main/java/net/rptools/maptool/client/ui/theme/RessourceManager.java b/src/main/java/net/rptools/maptool/client/ui/theme/RessourceManager.java index 829281df3b..4c1353ce1f 100644 --- a/src/main/java/net/rptools/maptool/client/ui/theme/RessourceManager.java +++ b/src/main/java/net/rptools/maptool/client/ui/theme/RessourceManager.java @@ -244,7 +244,6 @@ public class RessourceManager { put(Images.GRID_BORDER_HEX, IMAGE_DIR + "hexBorder.png"); put(Images.GRID_BORDER_ISOMETRIC, IMAGE_DIR + "isoBorder.png"); put(Images.GRID_BORDER_SQUARE, IMAGE_DIR + "whiteBorder.png"); - put(Images.GRID_BORDER_SQUARE_RED, IMAGE_DIR + "grid-square-red.png"); put(Images.HEROLABS_PORTRAIT, IMAGE_DIR + "powered_by_hero_lab_small.png"); put(Images.HEROLABS_TOKEN, IMAGE_DIR + "hero-lab-token.png"); put(Images.LIGHT_SOURCE, IMAGE_DIR + "lightbulb.png"); diff --git a/src/main/java/net/rptools/maptool/model/Grid.java b/src/main/java/net/rptools/maptool/model/Grid.java index db91d21349..a4b13aaba7 100644 --- a/src/main/java/net/rptools/maptool/model/Grid.java +++ b/src/main/java/net/rptools/maptool/model/Grid.java @@ -65,7 +65,7 @@ public abstract class Grid implements Cloneable { private static final Dimension NO_DIM = new Dimension(); private static final DirectionCalculator calculator = new DirectionCalculator(); - private static Map gridShapeCache = new ConcurrentHashMap<>(); + private static final Map gridShapeCache = new ConcurrentHashMap<>(); protected transient Map movementKeys = null; private transient Zone zone; diff --git a/src/main/java/net/rptools/maptool/model/HexGrid.java b/src/main/java/net/rptools/maptool/model/HexGrid.java index c29381a6cc..47dad64c1f 100644 --- a/src/main/java/net/rptools/maptool/model/HexGrid.java +++ b/src/main/java/net/rptools/maptool/model/HexGrid.java @@ -73,7 +73,8 @@ public boolean isCoordinatesSupported() { return false; } }; - protected static BufferedImage pathHighlight = RessourceManager.getImage(Images.GRID_BORDER_HEX); + protected static final BufferedImage pathHighlight = + RessourceManager.getImage(Images.GRID_BORDER_HEX); @Override public Point2D.Double getCenterOffset() { diff --git a/src/main/java/net/rptools/maptool/model/HexGridHorizontal.java b/src/main/java/net/rptools/maptool/model/HexGridHorizontal.java index 5079593096..5e139eddca 100644 --- a/src/main/java/net/rptools/maptool/model/HexGridHorizontal.java +++ b/src/main/java/net/rptools/maptool/model/HexGridHorizontal.java @@ -55,7 +55,7 @@ public class HexGridHorizontal extends HexGrid { } }; private static List footprintList; - private static Map gridShapeCache = new ConcurrentHashMap<>(); + private static final Map gridShapeCache = new ConcurrentHashMap<>(); @Override public boolean isHexHorizontal() { diff --git a/src/main/java/net/rptools/maptool/model/HexGridVertical.java b/src/main/java/net/rptools/maptool/model/HexGridVertical.java index 4370477b23..6946ff51a6 100644 --- a/src/main/java/net/rptools/maptool/model/HexGridVertical.java +++ b/src/main/java/net/rptools/maptool/model/HexGridVertical.java @@ -56,7 +56,7 @@ public class HexGridVertical extends HexGrid { } }; private static List footprintList; - private static Map gridShapeCache = new ConcurrentHashMap<>(); + private static final Map gridShapeCache = new ConcurrentHashMap<>(); @Override protected synchronized Map getGridShapeCache() { diff --git a/src/main/java/net/rptools/maptool/model/IsometricGrid.java b/src/main/java/net/rptools/maptool/model/IsometricGrid.java index c051caf3c8..c2eb9dc33c 100644 --- a/src/main/java/net/rptools/maptool/model/IsometricGrid.java +++ b/src/main/java/net/rptools/maptool/model/IsometricGrid.java @@ -40,7 +40,7 @@ public class IsometricGrid extends Grid { private static List footprintList; - private static BufferedImage pathHighlight = + private static final BufferedImage pathHighlight = RessourceManager.getImage(Images.GRID_BORDER_ISOMETRIC); public boolean isIsometric() { diff --git a/src/main/java/net/rptools/maptool/model/SquareGrid.java b/src/main/java/net/rptools/maptool/model/SquareGrid.java index db80dbd8b1..2d00c1863d 100644 --- a/src/main/java/net/rptools/maptool/model/SquareGrid.java +++ b/src/main/java/net/rptools/maptool/model/SquareGrid.java @@ -50,8 +50,6 @@ public class SquareGrid extends Grid { private static final String alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // $NON-NLS-1$ private static final Dimension CELL_OFFSET = new Dimension(0, 0); private static BufferedImage pathHighlight = RessourceManager.getImage(Images.GRID_BORDER_SQUARE); - private static BufferedImage pathHighlightAlt = - RessourceManager.getImage(Images.GRID_BORDER_SQUARE_RED); private static List footprintList; diff --git a/src/main/resources/net/rptools/maptool/client/image/grid-square-orage.png b/src/main/resources/net/rptools/maptool/client/image/grid-square-orage.png deleted file mode 100644 index 49dd11b95d6f7b77be1adcc354c23d979a05a3e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2583 zcmV+y3h4ETP)8o!TAh(RUB2nv4h>2lrL-QCl6 z_Z%+Yd9&VqIs4M5&$_lf@YMa!h2H0nzjy(7v$`xSa*X~ph4JU;`_Ts`zmNW&6U!ah zKK-lby6=S+SO|&T@+!+8 zSpLO;V;3v~6;R(``4P+GEMKgEl|t2i-2o-&l38jnS~82pxf&Q&ps_k9pO0An%<@~7 zzm|ZpV)UCVKVx}{<-Vf9Czo>nNNyVYH=S#4I`E$h95iI7WkV%V{Rro<&x+1+9mD2hvnNWKNgd8d}@oq)%E*+K@Vu0P~}>ssiVF< z2L%nG%0WTHe_-{z#j!of@+`|sqTWEi)SJalf9@VP40?__$FpE*;Fx+~v16}G=eXR; z=Wi^}v%I%D@xVjxu4RzXz_JKP-70mmf98$J8!JFX*TI0I^$DO+Pc7#L9nTAmaxt4h z&1{d&Lv{sd6lOQTB>T#yoeKrCViT;s@K=|JXzlBlw? zYUjzX3E6)4&PkDp2II~PE4v}`cLiWL$fZXZy3EFdT6MG03TVvC<}F24VTfIC%y^Lh zNA*(tC+xBsqx-1}aA?HQmYrgc*>H_IQ<4&>fiJ;E18eNo%o+elYK^(2AgaN-?SQop z3T>EF-=@uuDwY;-a$8JJD7x28KpD86i!mCb4*ty1fLbWYbCgP975Q1w0h_m`XmQJ~ zZGbsyb5`Tlz*rAt?wHJ~d)j29&ScCiGw|%N_Fg%t`!z2d)eFbuwODOkX~j=@%0UX+ z!&Ei?)C0Z4R6D3vE*o3yYr;^0EIMEZ^rY;911`X7P*t*U!KAi9;ia^T$l|btCcT9k z?1svI%l2aJpu&LNGqx#jA@9em)~31z&}_@(_mVpsu&@J~MsW&~04u|&hRl=`E$O~s z&WloijN5sJYwe8wwSaHOmLbuWFlangM-(YF%ouf`r&G4?tevL9Oy-i*Ck7C}d;z*s zqXkrRz~Zn2)kKTNZETd3Yp2#ca#Se+Bv;K~aTK;p%4}5kPa1yQX<5f4n`NLxC+2uD z^#!vJzDB*TDlDqSP{Aflwz1l-2h$eR`p#AAtaFlzCY4JzO6su8X+`e{@k*HMFAXf9 z*Mn{JIh$?f_mGBVPfkaU&7tGI`J-AE(sptrEU|F3VI3%zq0S_Mkz!=Rcs@@tl zM@H3qU<`2B=Gj{D=+k{+N)x@c%JNOblX>xiF@n!(lB~<@r#|cEc`s~nHQSBJ*;@0D&UG?K?TYg5^o8?V<`B2VM`nWl+{tE9kXp- zC#Db2zHO%5@=!6-aSZy>9scXB`&>tT{1k8I?chyhhdcLVv7j)bQS;R&odmbd_aImQV=Y>sMp1(Rx6^Z>nxF_@Ipq_SebT z?_IAM$ehH3kD)-caU;}1RLdV`(*SRARFeWKN<4i|2FjopL}!}s!qP;%D1b4O&{OBN zx(JKJzdpCy2grkC2p(#IQkPr$t`|FC&5WQ^^|+T7pjn-b;RIUkI+%*LCcWx8qSj<~ zSO+QqovPndD<|WgxjJaO7$_HKHZFsjI;I3@@6o{kN5eW2O*$Z}uGz8z<@1Y&qoo0~1rS@H*U6ZLqw=W+ zY_+`IyI9@}X}YI5ZtMw0*uZ9n3!q1G7aVtr>xwP-#3( znNwyp+wUaMLv>yoQw5{JHylhV7s`ukm+g#o~sN#xx?bphJPj6$G5m(0%| zfn7S=uKo6yy|#Ehn>`0h4^ybRq|;{9beTB6b5M0?IT~#oH8M69P+qg9B22KmOfsJw zFD0=5_4Pv7@Vt0|D%XoY;L6o9IJw{pI}5<-WxF3Hu+pf1`r5^%<9TOSr_@V&u2}~) zT6R0jaCxn)fBc7()TT1x$v{3YT)F%crrzt!2Z@q|-`mdsA=#}Z9meH@p>g$(TzUAxupuuo!RDh~|n4D}16VGm`@>1=$RC_7Z_idI}9G}^zv-=*) zZ+yvW95CGK&62sL^Zhzt`7m^y7%^jQE5&s#`2Cx{iNbIE-Fnj#ReZ zUpv>V6P?mi<#IoGyZ^%SYnHbd*D0${zA6P{LAxnPt z@_UxoB;1rhn_wMqTB#~4%Vptp`d!ZhRc3TIf7g?aKkztu%SC?f^6}K~dMeQJzj9P)Vgb_#x zBsPEq?DzusE53mpzr+`?0~Tx|-bfIFLI%N1o2qG7ca__Axo-&MN}hg9-M)3|9J^eP zo*sSr5m=vFAAg4Vkl3)X@*(*DK5ZSUU6-k@7e`NbD+rwP%^j zpY7*te@ehf08Px~``O;g_F}fPrW`d7ZqH}uf3lor(eG;it2oZM?(bxKx73}9+k@qQ z+|Bl0wpaMek~K0a9|3mCeKN{ebpo#UtBj)qoEo6dbF19t`((7FcRd2{HCpspk?Rs~ z?qz$sxM2l6HG%34E-(U0j=y>ViUz();=Dbgcn)&CT7RB}TbA<+jyw0WUE$?zME1=O zIO0MOs0aKAU>rnZEl_!u#P>?1{=6KgrUlLp0-c>me54EV7zJ@&uaonAz#nGB62?3=W0MnNn%iua0&n}?)l@y7k8Q;uzYboC}j&I8JUnJU(IhVU{(xx zp_G}La6OVN9khtQk^)+?ih*QA0+tPWUZb$|hp5db$r;rup(1BcnDwOVHEh@Q9A$D2 z%NZSiMFEJ^z=T2RlH;>C$7#Ssqyyg@8qY(e-b5Rt*f2{&`AWbNAc;6clOtbnf|boU zN@mfI$cPO<+L&E<#z8kjPDJ1R?xcK~PV!xp>|)>NqS^ISP{hDt1&z!YnZBy|DJlLA zmd`>kE!1RNDlG4h`dZ|2xB}lm%Z`Kw35j-(G-kbI;0VyUVX`TMj`Rpt_F7;kj!@}BK8dLlML1>ti8{| z!5X1(1x6v6dISFbRH_5gZpBrW9jkpKESP3yvbUT2s?_`b8HaIXNK`t3)tb{fnGx=z zbsNB{?bA#U2?&G+6T!Nt1GDw}@#x6Ed%vTV!Ro7RYN<64golTr&k?XswglBS3gW!0 zBtG?;{0Cr_L0yhNX$OyYD9bzuox28 zHp8HlQz}j^VA_C^m{{8yNN-4@qpN`C`Yak_L^6;g{z)w13ff>+1qmHceWBT6QN5mmYoxY6Y+cR<7`jy(DPeTfBtX;Nmt(A| z^_`R!&;XL-{K9i-Th^dvY6SHGwd#j57|~(r4w(f-=NRa)E0c9P6`FOMZ7qk?OU@TpInGiwWBm!n+k1>0M>GJuzW%d%5y^G$ws|Q$HO!5|DBWcE z=x9AA>aPI*&fdi$;Rgjv;@#F|GviON-{~1?LHQ0rQjSR}pPVwNJTo0+6q(2$iklXk zZGid~mc7RJjNpWZgkd0II4I*&q^wE1{N}@K-!Z<$N15G6*}i6$=|BgL1BP(Gil>Zp z@b_DwzcZf8`mVq_2bFKktnDxPcs1~q6abeF$~XnUkGh#s?_aZhRRR;^+Rq#3e_V6H zFS32ZUDB0cIt^I!&QlN=27W_E+$&b)eXf58^oBL Date: Sat, 28 Sep 2024 14:52:15 -0700 Subject: [PATCH 031/146] Add new Grid methods for snapped facing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tools no longer call `Grid.getFacingAngles()`, which requires each caller to interpret the resposne to get the nearest or next facing. Now there are two new methods in on `Grid`: - `int nearestFacing(int facing, boolean faceEdges, boolean faceVertices)`: snaps a facing to one of the standard facings. - `int nextFacing(int facing, boolean faceEdges, boolean faceVertices, boolean clockwise)`: snaps a facing as in `nearestFacing()`, but also advances one step clockwise or counterclockwise. There is no new behaviour introduced by this change. Also included are tests cases for interesting input facings. These make it clear there are some bugs in the current behaviour, such as -179° being considered closer to -135° than to 180° (these are 44° and 1° away, respectively). --- .../maptool/client/tool/DefaultTool.java | 22 +- .../maptool/client/tool/FacingTool.java | 6 +- .../maptool/client/tool/PointerTool.java | 20 +- .../java/net/rptools/maptool/model/Grid.java | 32 +- .../maptool/model/GridlessGridTest.java | 48 ++ .../maptool/model/HexGridHorizontalTest.java | 48 ++ .../maptool/model/HexGridVerticalTest.java | 48 ++ .../maptool/model/IsometricGridTest.java | 48 ++ .../rptools/maptool/model/SquareGridTest.java | 48 ++ .../model/GridlessGrid.nearestFacing.csv | 257 +++++++++ .../maptool/model/GridlessGrid.nextFacing.csv | 513 ++++++++++++++++++ .../model/HexGridHorizontal.nearestFacing.csv | 165 ++++++ .../model/HexGridHorizontal.nextFacing.csv | 329 +++++++++++ .../model/HexGridVertical.nearestFacing.csv | 165 ++++++ .../model/HexGridVertical.nextFacing.csv | 329 +++++++++++ .../model/IsometricGrid.nearestFacing.csv | 195 +++++++ .../model/IsometricGrid.nextFacing.csv | 380 +++++++++++++ .../model/SquareGrid.nearestFacing.csv | 195 +++++++ .../maptool/model/SquareGrid.nextFacing.csv | 380 +++++++++++++ 19 files changed, 3195 insertions(+), 33 deletions(-) create mode 100644 src/test/java/net/rptools/maptool/model/GridlessGridTest.java create mode 100644 src/test/java/net/rptools/maptool/model/HexGridHorizontalTest.java create mode 100644 src/test/java/net/rptools/maptool/model/HexGridVerticalTest.java create mode 100644 src/test/java/net/rptools/maptool/model/IsometricGridTest.java create mode 100644 src/test/java/net/rptools/maptool/model/SquareGridTest.java create mode 100644 src/test/resources/net/rptools/maptool/model/GridlessGrid.nearestFacing.csv create mode 100644 src/test/resources/net/rptools/maptool/model/GridlessGrid.nextFacing.csv create mode 100644 src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nearestFacing.csv create mode 100644 src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nextFacing.csv create mode 100644 src/test/resources/net/rptools/maptool/model/HexGridVertical.nearestFacing.csv create mode 100644 src/test/resources/net/rptools/maptool/model/HexGridVertical.nextFacing.csv create mode 100644 src/test/resources/net/rptools/maptool/model/IsometricGrid.nearestFacing.csv create mode 100644 src/test/resources/net/rptools/maptool/model/IsometricGrid.nextFacing.csv create mode 100644 src/test/resources/net/rptools/maptool/model/SquareGrid.nearestFacing.csv create mode 100644 src/test/resources/net/rptools/maptool/model/SquareGrid.nextFacing.csv diff --git a/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java b/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java index e5f1c4b654..26e7c3f3d4 100644 --- a/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java @@ -33,7 +33,6 @@ import net.rptools.maptool.model.Token; import net.rptools.maptool.model.ViewMovementKey; import net.rptools.maptool.model.Zone; -import net.rptools.maptool.util.TokenUtil; /** */ public abstract class DefaultTool extends Tool @@ -380,20 +379,15 @@ public void mouseWheelMoved(MouseWheelEvent e) { facing += e.getWheelRotation() > 0 ? 5 : -5; } } else { - int[] facingArray = - getZone() + facing = + renderer + .getZone() .getGrid() - .getFacingAngles(AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); - int facingIndex = TokenUtil.getIndexNearestTo(facingArray, facing); - - facingIndex += e.getWheelRotation() > 0 ? 1 : -1; - if (facingIndex < 0) { - facingIndex = facingArray.length - 1; - } - if (facingIndex == facingArray.length) { - facingIndex = 0; - } - facing = facingArray[facingIndex]; + .nextFacing( + facing, + AppPreferences.getFaceEdge(), + AppPreferences.getFaceVertex(), + e.getWheelRotation() <= 0); } token.setFacing(facing); diff --git a/src/main/java/net/rptools/maptool/client/tool/FacingTool.java b/src/main/java/net/rptools/maptool/client/tool/FacingTool.java index bffcb3eb75..5b4668ad87 100644 --- a/src/main/java/net/rptools/maptool/client/tool/FacingTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/FacingTool.java @@ -29,7 +29,6 @@ import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.GUID; import net.rptools.maptool.model.Token; -import net.rptools.maptool.util.TokenUtil; /** */ public class FacingTool extends DefaultTool { @@ -101,12 +100,11 @@ public void mouseMoved(MouseEvent e) { int degrees = (int) Math.toDegrees(angle); if (!SwingUtil.isControlDown(e)) { - int[] facingAngles = + degrees = renderer .getZone() .getGrid() - .getFacingAngles(AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); - degrees = facingAngles[TokenUtil.getIndexNearestTo(facingAngles, degrees)]; + .nearestFacing(degrees, AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); } Area visibleArea = null; Set remoteSelected = new HashSet(); 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 3d108958a2..5e6edd6b30 100644 --- a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java @@ -58,7 +58,6 @@ import net.rptools.maptool.util.GraphicsUtil; import net.rptools.maptool.util.ImageManager; import net.rptools.maptool.util.StringUtil; -import net.rptools.maptool.util.TokenUtil; import org.apache.commons.lang.StringUtils; /** @@ -1100,22 +1099,15 @@ private void handleKeyRotate(int direction, boolean freeRotate) { if (freeRotate) { facing += direction * 5; } else { - int[] facingArray = + facing = renderer .getZone() .getGrid() - .getFacingAngles(AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); - int facingIndex = TokenUtil.getIndexNearestTo(facingArray, facing); - - facingIndex += direction; - - if (facingIndex < 0) { - facingIndex = facingArray.length - 1; - } - if (facingIndex == facingArray.length) { - facingIndex = 0; - } - facing = facingArray[facingIndex]; + .nextFacing( + facing, + AppPreferences.getFaceEdge(), + AppPreferences.getFaceVertex(), + direction < 0); } MapTool.serverCommand().updateTokenProperty(token, Token.Update.setFacing, facing); } diff --git a/src/main/java/net/rptools/maptool/model/Grid.java b/src/main/java/net/rptools/maptool/model/Grid.java index a4b13aaba7..4f36e06c05 100644 --- a/src/main/java/net/rptools/maptool/model/Grid.java +++ b/src/main/java/net/rptools/maptool/model/Grid.java @@ -41,6 +41,7 @@ import net.rptools.maptool.model.zones.GridChanged; import net.rptools.maptool.server.proto.GridDto; import net.rptools.maptool.util.GraphicsUtil; +import net.rptools.maptool.util.TokenUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -111,6 +112,35 @@ public void drawCoordinatesOverlay(Graphics2D g, ZoneRenderer renderer) { // Do nothing -- my default } + /** + * Get the next standard facing in the given direction. + * + * @param facing The current facing. + * @param faceEdges Whether to snap facing to edges. + * @param faceVertices + * @param clockwise + * @return + */ + public int nextFacing(int facing, boolean faceEdges, boolean faceVertices, boolean clockwise) { + int[] facingArray = getFacingAngles(faceEdges, faceVertices); + int facingIndex = TokenUtil.getIndexNearestTo(facingArray, facing); + + facingIndex += clockwise ? -1 : 1; + if (facingIndex < 0) { + facingIndex = facingArray.length - 1; + } + if (facingIndex == facingArray.length) { + facingIndex = 0; + } + return facingArray[facingIndex]; + } + + public int nearestFacing(int facing, boolean faceEdges, boolean faceVertices) { + int[] facingArray = getFacingAngles(faceEdges, faceVertices); + // TODO Much chance of overflow here. + return facingArray[TokenUtil.getIndexNearestTo(facingArray, facing)]; + } + /** * Get the facing options for tokens/objects on a grid. Each grid type can providing facings to * the edges, the vertices, both, or neither. @@ -121,7 +151,7 @@ public void drawCoordinatesOverlay(Graphics2D g, ZoneRenderer renderer) { * @param faceEdges - Tokens can face edges. * @param faceVertices - Tokens can face vertices. */ - public abstract int[] getFacingAngles(boolean faceEdges, boolean faceVertices); + protected abstract int[] getFacingAngles(boolean faceEdges, boolean faceVertices); /** * Return the Point (double precision) for pixel center of Cell diff --git a/src/test/java/net/rptools/maptool/model/GridlessGridTest.java b/src/test/java/net/rptools/maptool/model/GridlessGridTest.java new file mode 100644 index 0000000000..06e4a317f0 --- /dev/null +++ b/src/test/java/net/rptools/maptool/model/GridlessGridTest.java @@ -0,0 +1,48 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.model; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvFileSource; + +public class GridlessGridTest { + private GridlessGrid grid; + + @BeforeEach + void setUp() { + this.grid = new GridlessGrid(); + } + + @ParameterizedTest + @DisplayName("Test GridlessGrid.nextFacing(…)") + @CsvFileSource(resources = "GridlessGrid.nextFacing.csv", useHeadersInDisplayName = true) + void testNextFacing( + boolean faceEdges, boolean faceVertices, boolean clockwise, int input, int expected) { + int result = grid.nextFacing(input, faceEdges, faceVertices, clockwise); + assertEquals(expected, result); + } + + @ParameterizedTest + @DisplayName("Test GridlessGrid.nearestFacing(…)") + @CsvFileSource(resources = "GridlessGrid.nearestFacing.csv", useHeadersInDisplayName = true) + void testNearestFacing(boolean faceEdges, boolean faceVertices, int input, int expected) { + int result = grid.nearestFacing(input, faceEdges, faceVertices); + assertEquals(expected, result); + } +} diff --git a/src/test/java/net/rptools/maptool/model/HexGridHorizontalTest.java b/src/test/java/net/rptools/maptool/model/HexGridHorizontalTest.java new file mode 100644 index 0000000000..e8b43ebac4 --- /dev/null +++ b/src/test/java/net/rptools/maptool/model/HexGridHorizontalTest.java @@ -0,0 +1,48 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.model; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvFileSource; + +public class HexGridHorizontalTest { + private HexGridHorizontal grid; + + @BeforeEach + void setUp() { + this.grid = new HexGridHorizontal(); + } + + @ParameterizedTest + @DisplayName("Test HexGridHorizontal.nextFacing(…)") + @CsvFileSource(resources = "HexGridHorizontal.nextFacing.csv", useHeadersInDisplayName = true) + void testNextFacing( + boolean faceEdges, boolean faceVertices, boolean clockwise, int input, int expected) { + int result = grid.nextFacing(input, faceEdges, faceVertices, clockwise); + assertEquals(expected, result); + } + + @ParameterizedTest + @DisplayName("Test HexGridHorizontal.nearestFacing(…)") + @CsvFileSource(resources = "HexGridHorizontal.nearestFacing.csv", useHeadersInDisplayName = true) + void testNearestFacing(boolean faceEdges, boolean faceVertices, int input, int expected) { + int result = grid.nearestFacing(input, faceEdges, faceVertices); + assertEquals(expected, result); + } +} diff --git a/src/test/java/net/rptools/maptool/model/HexGridVerticalTest.java b/src/test/java/net/rptools/maptool/model/HexGridVerticalTest.java new file mode 100644 index 0000000000..57951df942 --- /dev/null +++ b/src/test/java/net/rptools/maptool/model/HexGridVerticalTest.java @@ -0,0 +1,48 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.model; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvFileSource; + +public class HexGridVerticalTest { + private HexGridVertical grid; + + @BeforeEach + void setUp() { + this.grid = new HexGridVertical(); + } + + @ParameterizedTest + @DisplayName("Test HexGridVertical.nextFacing(…)") + @CsvFileSource(resources = "HexGridVertical.nextFacing.csv", useHeadersInDisplayName = true) + void testNextFacing( + boolean faceEdges, boolean faceVertices, boolean clockwise, int input, int expected) { + int result = grid.nextFacing(input, faceEdges, faceVertices, clockwise); + assertEquals(expected, result); + } + + @ParameterizedTest + @DisplayName("Test HexGridVertical.nearestFacing(…)") + @CsvFileSource(resources = "HexGridVertical.nearestFacing.csv", useHeadersInDisplayName = true) + void testNearestFacing(boolean faceEdges, boolean faceVertices, int input, int expected) { + int result = grid.nearestFacing(input, faceEdges, faceVertices); + assertEquals(expected, result); + } +} diff --git a/src/test/java/net/rptools/maptool/model/IsometricGridTest.java b/src/test/java/net/rptools/maptool/model/IsometricGridTest.java new file mode 100644 index 0000000000..9fc5067086 --- /dev/null +++ b/src/test/java/net/rptools/maptool/model/IsometricGridTest.java @@ -0,0 +1,48 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.model; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvFileSource; + +public class IsometricGridTest { + private IsometricGrid grid; + + @BeforeEach + void setUp() { + this.grid = new IsometricGrid(); + } + + @ParameterizedTest + @DisplayName("Test IsometricGrid.nextFacing(…)") + @CsvFileSource(resources = "IsometricGrid.nextFacing.csv", useHeadersInDisplayName = true) + void testNextFacing( + boolean faceEdges, boolean faceVertices, boolean clockwise, int input, int expected) { + int result = grid.nextFacing(input, faceEdges, faceVertices, clockwise); + assertEquals(expected, result); + } + + @ParameterizedTest + @DisplayName("Test IsometricGrid.nearestFacing(…)") + @CsvFileSource(resources = "IsometricGrid.nearestFacing.csv", useHeadersInDisplayName = true) + void testNearestFacing(boolean faceEdges, boolean faceVertices, int input, int expected) { + int result = grid.nearestFacing(input, faceEdges, faceVertices); + assertEquals(expected, result); + } +} diff --git a/src/test/java/net/rptools/maptool/model/SquareGridTest.java b/src/test/java/net/rptools/maptool/model/SquareGridTest.java new file mode 100644 index 0000000000..a5a15f8494 --- /dev/null +++ b/src/test/java/net/rptools/maptool/model/SquareGridTest.java @@ -0,0 +1,48 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.model; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvFileSource; + +public class SquareGridTest { + private SquareGrid grid; + + @BeforeEach + void setUp() { + this.grid = new SquareGrid(); + } + + @ParameterizedTest + @DisplayName("Test SquareGrid.nextFacing(…)") + @CsvFileSource(resources = "SquareGrid.nextFacing.csv", useHeadersInDisplayName = true) + void testNextFacing( + boolean faceEdges, boolean faceVertices, boolean clockwise, int input, int expected) { + int result = grid.nextFacing(input, faceEdges, faceVertices, clockwise); + assertEquals(expected, result); + } + + @ParameterizedTest + @DisplayName("Test SquareGrid.nearestFacing(…)") + @CsvFileSource(resources = "SquareGrid.nearestFacing.csv", useHeadersInDisplayName = true) + void testNearestFacing(boolean faceEdges, boolean faceVertices, int input, int expected) { + int result = grid.nearestFacing(input, faceEdges, faceVertices); + assertEquals(expected, result); + } +} diff --git a/src/test/resources/net/rptools/maptool/model/GridlessGrid.nearestFacing.csv b/src/test/resources/net/rptools/maptool/model/GridlessGrid.nearestFacing.csv new file mode 100644 index 0000000000..ea71e1c8ed --- /dev/null +++ b/src/test/resources/net/rptools/maptool/model/GridlessGrid.nearestFacing.csv @@ -0,0 +1,257 @@ +faceEdges,faceVertices,facing,expected + +false,false,-179,-135 +false,false,-178,-135 +false,false,-158,-135 +false,false,-157,-135 +false,false,-156,-135 +false,false,-136,-135 +false,false,-135,-135 +false,false,-134,-135 +false,false,-114,-135 +false,false,-113,-135 +false,false,-112,-90 +false,false,-111,-90 +false,false,-110,-90 +false,false,-91,-90 +false,false,-90,-90 +false,false,-89,-90 +false,false,-69,-90 +false,false,-68,-90 +false,false,-67,-45 +false,false,-66,-45 +false,false,-65,-45 +false,false,-46,-45 +false,false,-45,-45 +false,false,-44,-45 +false,false,-24,-45 +false,false,-23,-45 +false,false,-22,0 +false,false,-21,0 +false,false,-20,0 +false,false,-1,0 +false,false,0,0 +false,false,1,0 +false,false,21,0 +false,false,22,0 +false,false,23,45 +false,false,24,45 +false,false,25,45 +false,false,44,45 +false,false,45,45 +false,false,46,45 +false,false,66,45 +false,false,67,45 +false,false,68,90 +false,false,69,90 +false,false,70,90 +false,false,89,90 +false,false,90,90 +false,false,91,90 +false,false,111,90 +false,false,112,90 +false,false,113,135 +false,false,114,135 +false,false,115,135 +false,false,134,135 +false,false,135,135 +false,false,136,135 +false,false,156,135 +false,false,157,135 +false,false,158,180 +false,false,159,180 +false,false,160,180 +false,false,179,180 +false,false,180,180 + +true,false,-179,-135 +true,false,-178,-135 +true,false,-158,-135 +true,false,-157,-135 +true,false,-156,-135 +true,false,-136,-135 +true,false,-135,-135 +true,false,-134,-135 +true,false,-114,-135 +true,false,-113,-135 +true,false,-112,-90 +true,false,-111,-90 +true,false,-110,-90 +true,false,-91,-90 +true,false,-90,-90 +true,false,-89,-90 +true,false,-69,-90 +true,false,-68,-90 +true,false,-67,-45 +true,false,-66,-45 +true,false,-65,-45 +true,false,-46,-45 +true,false,-45,-45 +true,false,-44,-45 +true,false,-24,-45 +true,false,-23,-45 +true,false,-22,0 +true,false,-21,0 +true,false,-20,0 +true,false,-1,0 +true,false,0,0 +true,false,1,0 +true,false,21,0 +true,false,22,0 +true,false,23,45 +true,false,24,45 +true,false,25,45 +true,false,44,45 +true,false,45,45 +true,false,46,45 +true,false,66,45 +true,false,67,45 +true,false,68,90 +true,false,69,90 +true,false,70,90 +true,false,89,90 +true,false,90,90 +true,false,91,90 +true,false,111,90 +true,false,112,90 +true,false,113,135 +true,false,114,135 +true,false,115,135 +true,false,134,135 +true,false,135,135 +true,false,136,135 +true,false,156,135 +true,false,157,135 +true,false,158,180 +true,false,159,180 +true,false,160,180 +true,false,179,180 +true,false,180,180 + +false,true,-179,-135 +false,true,-178,-135 +false,true,-158,-135 +false,true,-157,-135 +false,true,-156,-135 +false,true,-136,-135 +false,true,-135,-135 +false,true,-134,-135 +false,true,-114,-135 +false,true,-113,-135 +false,true,-112,-90 +false,true,-111,-90 +false,true,-110,-90 +false,true,-91,-90 +false,true,-90,-90 +false,true,-89,-90 +false,true,-69,-90 +false,true,-68,-90 +false,true,-67,-45 +false,true,-66,-45 +false,true,-65,-45 +false,true,-46,-45 +false,true,-45,-45 +false,true,-44,-45 +false,true,-24,-45 +false,true,-23,-45 +false,true,-22,0 +false,true,-21,0 +false,true,-20,0 +false,true,-1,0 +false,true,0,0 +false,true,1,0 +false,true,21,0 +false,true,22,0 +false,true,23,45 +false,true,24,45 +false,true,25,45 +false,true,44,45 +false,true,45,45 +false,true,46,45 +false,true,66,45 +false,true,67,45 +false,true,68,90 +false,true,69,90 +false,true,70,90 +false,true,89,90 +false,true,90,90 +false,true,91,90 +false,true,111,90 +false,true,112,90 +false,true,113,135 +false,true,114,135 +false,true,115,135 +false,true,134,135 +false,true,135,135 +false,true,136,135 +false,true,156,135 +false,true,157,135 +false,true,158,180 +false,true,159,180 +false,true,160,180 +false,true,179,180 +false,true,180,180 + +true,true,-179,-135 +true,true,-178,-135 +true,true,-158,-135 +true,true,-157,-135 +true,true,-156,-135 +true,true,-136,-135 +true,true,-135,-135 +true,true,-134,-135 +true,true,-114,-135 +true,true,-113,-135 +true,true,-112,-90 +true,true,-111,-90 +true,true,-110,-90 +true,true,-91,-90 +true,true,-90,-90 +true,true,-89,-90 +true,true,-69,-90 +true,true,-68,-90 +true,true,-67,-45 +true,true,-66,-45 +true,true,-65,-45 +true,true,-46,-45 +true,true,-45,-45 +true,true,-44,-45 +true,true,-24,-45 +true,true,-23,-45 +true,true,-22,0 +true,true,-21,0 +true,true,-20,0 +true,true,-1,0 +true,true,0,0 +true,true,1,0 +true,true,21,0 +true,true,22,0 +true,true,23,45 +true,true,24,45 +true,true,25,45 +true,true,44,45 +true,true,45,45 +true,true,46,45 +true,true,66,45 +true,true,67,45 +true,true,68,90 +true,true,69,90 +true,true,70,90 +true,true,89,90 +true,true,90,90 +true,true,91,90 +true,true,111,90 +true,true,112,90 +true,true,113,135 +true,true,114,135 +true,true,115,135 +true,true,134,135 +true,true,135,135 +true,true,136,135 +true,true,156,135 +true,true,157,135 +true,true,158,180 +true,true,159,180 +true,true,160,180 +true,true,179,180 +true,true,180,180 diff --git a/src/test/resources/net/rptools/maptool/model/GridlessGrid.nextFacing.csv b/src/test/resources/net/rptools/maptool/model/GridlessGrid.nextFacing.csv new file mode 100644 index 0000000000..0ae615e38b --- /dev/null +++ b/src/test/resources/net/rptools/maptool/model/GridlessGrid.nextFacing.csv @@ -0,0 +1,513 @@ +faceEdges,faceVertices,clockwise,facing,expected + +false,false,false,-179,-90 +false,false,false,-178,-90 +false,false,false,-158,-90 +false,false,false,-157,-90 +false,false,false,-156,-90 +false,false,false,-136,-90 +false,false,false,-135,-90 +false,false,false,-134,-90 +false,false,false,-114,-90 +false,false,false,-113,-90 +false,false,false,-112,-45 +false,false,false,-111,-45 +false,false,false,-110,-45 +false,false,false,-91,-45 +false,false,false,-90,-45 +false,false,false,-89,-45 +false,false,false,-69,-45 +false,false,false,-68,-45 +false,false,false,-67,0 +false,false,false,-66,0 +false,false,false,-65,0 +false,false,false,-46,0 +false,false,false,-45,0 +false,false,false,-44,0 +false,false,false,-24,0 +false,false,false,-23,0 +false,false,false,-22,45 +false,false,false,-21,45 +false,false,false,-20,45 +false,false,false,-1,45 +false,false,false,0,45 +false,false,false,1,45 +false,false,false,21,45 +false,false,false,22,45 +false,false,false,23,90 +false,false,false,24,90 +false,false,false,25,90 +false,false,false,44,90 +false,false,false,45,90 +false,false,false,46,90 +false,false,false,66,90 +false,false,false,67,90 +false,false,false,68,135 +false,false,false,69,135 +false,false,false,70,135 +false,false,false,89,135 +false,false,false,90,135 +false,false,false,91,135 +false,false,false,111,135 +false,false,false,112,135 +false,false,false,113,180 +false,false,false,114,180 +false,false,false,115,180 +false,false,false,134,180 +false,false,false,135,180 +false,false,false,136,180 +false,false,false,156,180 +false,false,false,157,180 +false,false,false,158,-135 +false,false,false,159,-135 +false,false,false,160,-135 +false,false,false,179,-135 +false,false,false,180,-135 + +false,false,true,-179,180 +false,false,true,-178,180 +false,false,true,-158,180 +false,false,true,-157,180 +false,false,true,-156,180 +false,false,true,-136,180 +false,false,true,-135,180 +false,false,true,-134,180 +false,false,true,-114,180 +false,false,true,-113,180 +false,false,true,-112,-135 +false,false,true,-111,-135 +false,false,true,-110,-135 +false,false,true,-91,-135 +false,false,true,-90,-135 +false,false,true,-89,-135 +false,false,true,-69,-135 +false,false,true,-68,-135 +false,false,true,-67,-90 +false,false,true,-66,-90 +false,false,true,-65,-90 +false,false,true,-46,-90 +false,false,true,-45,-90 +false,false,true,-44,-90 +false,false,true,-24,-90 +false,false,true,-23,-90 +false,false,true,-22,-45 +false,false,true,-21,-45 +false,false,true,-20,-45 +false,false,true,-1,-45 +false,false,true,0,-45 +false,false,true,1,-45 +false,false,true,21,-45 +false,false,true,22,-45 +false,false,true,23,0 +false,false,true,24,0 +false,false,true,25,0 +false,false,true,44,0 +false,false,true,45,0 +false,false,true,46,0 +false,false,true,66,0 +false,false,true,67,0 +false,false,true,68,45 +false,false,true,69,45 +false,false,true,70,45 +false,false,true,89,45 +false,false,true,90,45 +false,false,true,91,45 +false,false,true,111,45 +false,false,true,112,45 +false,false,true,113,90 +false,false,true,114,90 +false,false,true,115,90 +false,false,true,134,90 +false,false,true,135,90 +false,false,true,136,90 +false,false,true,156,90 +false,false,true,157,90 +false,false,true,158,135 +false,false,true,159,135 +false,false,true,160,135 +false,false,true,179,135 +false,false,true,180,135 + +true,false,false,-179,-90 +true,false,false,-178,-90 +true,false,false,-158,-90 +true,false,false,-157,-90 +true,false,false,-156,-90 +true,false,false,-136,-90 +true,false,false,-135,-90 +true,false,false,-134,-90 +true,false,false,-114,-90 +true,false,false,-113,-90 +true,false,false,-112,-45 +true,false,false,-111,-45 +true,false,false,-110,-45 +true,false,false,-91,-45 +true,false,false,-90,-45 +true,false,false,-89,-45 +true,false,false,-69,-45 +true,false,false,-68,-45 +true,false,false,-67,0 +true,false,false,-66,0 +true,false,false,-65,0 +true,false,false,-46,0 +true,false,false,-45,0 +true,false,false,-44,0 +true,false,false,-24,0 +true,false,false,-23,0 +true,false,false,-22,45 +true,false,false,-21,45 +true,false,false,-20,45 +true,false,false,-1,45 +true,false,false,0,45 +true,false,false,1,45 +true,false,false,21,45 +true,false,false,22,45 +true,false,false,23,90 +true,false,false,24,90 +true,false,false,25,90 +true,false,false,44,90 +true,false,false,45,90 +true,false,false,46,90 +true,false,false,66,90 +true,false,false,67,90 +true,false,false,68,135 +true,false,false,69,135 +true,false,false,70,135 +true,false,false,89,135 +true,false,false,90,135 +true,false,false,91,135 +true,false,false,111,135 +true,false,false,112,135 +true,false,false,113,180 +true,false,false,114,180 +true,false,false,115,180 +true,false,false,134,180 +true,false,false,135,180 +true,false,false,136,180 +true,false,false,156,180 +true,false,false,157,180 +true,false,false,158,-135 +true,false,false,159,-135 +true,false,false,160,-135 +true,false,false,179,-135 +true,false,false,180,-135 + +true,false,true,-179,180 +true,false,true,-178,180 +true,false,true,-158,180 +true,false,true,-157,180 +true,false,true,-156,180 +true,false,true,-136,180 +true,false,true,-135,180 +true,false,true,-134,180 +true,false,true,-114,180 +true,false,true,-113,180 +true,false,true,-112,-135 +true,false,true,-111,-135 +true,false,true,-110,-135 +true,false,true,-91,-135 +true,false,true,-90,-135 +true,false,true,-89,-135 +true,false,true,-69,-135 +true,false,true,-68,-135 +true,false,true,-67,-90 +true,false,true,-66,-90 +true,false,true,-65,-90 +true,false,true,-46,-90 +true,false,true,-45,-90 +true,false,true,-44,-90 +true,false,true,-24,-90 +true,false,true,-23,-90 +true,false,true,-22,-45 +true,false,true,-21,-45 +true,false,true,-20,-45 +true,false,true,-1,-45 +true,false,true,0,-45 +true,false,true,1,-45 +true,false,true,21,-45 +true,false,true,22,-45 +true,false,true,23,0 +true,false,true,24,0 +true,false,true,25,0 +true,false,true,44,0 +true,false,true,45,0 +true,false,true,46,0 +true,false,true,66,0 +true,false,true,67,0 +true,false,true,68,45 +true,false,true,69,45 +true,false,true,70,45 +true,false,true,89,45 +true,false,true,90,45 +true,false,true,91,45 +true,false,true,111,45 +true,false,true,112,45 +true,false,true,113,90 +true,false,true,114,90 +true,false,true,115,90 +true,false,true,134,90 +true,false,true,135,90 +true,false,true,136,90 +true,false,true,156,90 +true,false,true,157,90 +true,false,true,158,135 +true,false,true,159,135 +true,false,true,160,135 +true,false,true,179,135 +true,false,true,180,135 + +false,true,false,-179,-90 +false,true,false,-178,-90 +false,true,false,-158,-90 +false,true,false,-157,-90 +false,true,false,-156,-90 +false,true,false,-136,-90 +false,true,false,-135,-90 +false,true,false,-134,-90 +false,true,false,-114,-90 +false,true,false,-113,-90 +false,true,false,-112,-45 +false,true,false,-111,-45 +false,true,false,-110,-45 +false,true,false,-91,-45 +false,true,false,-90,-45 +false,true,false,-89,-45 +false,true,false,-69,-45 +false,true,false,-68,-45 +false,true,false,-67,0 +false,true,false,-66,0 +false,true,false,-65,0 +false,true,false,-46,0 +false,true,false,-45,0 +false,true,false,-44,0 +false,true,false,-24,0 +false,true,false,-23,0 +false,true,false,-22,45 +false,true,false,-21,45 +false,true,false,-20,45 +false,true,false,-1,45 +false,true,false,0,45 +false,true,false,1,45 +false,true,false,21,45 +false,true,false,22,45 +false,true,false,23,90 +false,true,false,24,90 +false,true,false,25,90 +false,true,false,44,90 +false,true,false,45,90 +false,true,false,46,90 +false,true,false,66,90 +false,true,false,67,90 +false,true,false,68,135 +false,true,false,69,135 +false,true,false,70,135 +false,true,false,89,135 +false,true,false,90,135 +false,true,false,91,135 +false,true,false,111,135 +false,true,false,112,135 +false,true,false,113,180 +false,true,false,114,180 +false,true,false,115,180 +false,true,false,134,180 +false,true,false,135,180 +false,true,false,136,180 +false,true,false,156,180 +false,true,false,157,180 +false,true,false,158,-135 +false,true,false,159,-135 +false,true,false,160,-135 +false,true,false,179,-135 +false,true,false,180,-135 + +false,true,true,-179,180 +false,true,true,-178,180 +false,true,true,-158,180 +false,true,true,-157,180 +false,true,true,-156,180 +false,true,true,-136,180 +false,true,true,-135,180 +false,true,true,-134,180 +false,true,true,-114,180 +false,true,true,-113,180 +false,true,true,-112,-135 +false,true,true,-111,-135 +false,true,true,-110,-135 +false,true,true,-91,-135 +false,true,true,-90,-135 +false,true,true,-89,-135 +false,true,true,-69,-135 +false,true,true,-68,-135 +false,true,true,-67,-90 +false,true,true,-66,-90 +false,true,true,-65,-90 +false,true,true,-46,-90 +false,true,true,-45,-90 +false,true,true,-44,-90 +false,true,true,-24,-90 +false,true,true,-23,-90 +false,true,true,-22,-45 +false,true,true,-21,-45 +false,true,true,-20,-45 +false,true,true,-1,-45 +false,true,true,0,-45 +false,true,true,1,-45 +false,true,true,21,-45 +false,true,true,22,-45 +false,true,true,23,0 +false,true,true,24,0 +false,true,true,25,0 +false,true,true,44,0 +false,true,true,45,0 +false,true,true,46,0 +false,true,true,66,0 +false,true,true,67,0 +false,true,true,68,45 +false,true,true,69,45 +false,true,true,70,45 +false,true,true,89,45 +false,true,true,90,45 +false,true,true,91,45 +false,true,true,111,45 +false,true,true,112,45 +false,true,true,113,90 +false,true,true,114,90 +false,true,true,115,90 +false,true,true,134,90 +false,true,true,135,90 +false,true,true,136,90 +false,true,true,156,90 +false,true,true,157,90 +false,true,true,158,135 +false,true,true,159,135 +false,true,true,160,135 +false,true,true,179,135 +false,true,true,180,135 + +true,true,false,-179,-90 +true,true,false,-178,-90 +true,true,false,-158,-90 +true,true,false,-157,-90 +true,true,false,-156,-90 +true,true,false,-136,-90 +true,true,false,-135,-90 +true,true,false,-134,-90 +true,true,false,-114,-90 +true,true,false,-113,-90 +true,true,false,-112,-45 +true,true,false,-111,-45 +true,true,false,-110,-45 +true,true,false,-91,-45 +true,true,false,-90,-45 +true,true,false,-89,-45 +true,true,false,-69,-45 +true,true,false,-68,-45 +true,true,false,-67,0 +true,true,false,-66,0 +true,true,false,-65,0 +true,true,false,-46,0 +true,true,false,-45,0 +true,true,false,-44,0 +true,true,false,-24,0 +true,true,false,-23,0 +true,true,false,-22,45 +true,true,false,-21,45 +true,true,false,-20,45 +true,true,false,-1,45 +true,true,false,0,45 +true,true,false,1,45 +true,true,false,21,45 +true,true,false,22,45 +true,true,false,23,90 +true,true,false,24,90 +true,true,false,25,90 +true,true,false,44,90 +true,true,false,45,90 +true,true,false,46,90 +true,true,false,66,90 +true,true,false,67,90 +true,true,false,68,135 +true,true,false,69,135 +true,true,false,70,135 +true,true,false,89,135 +true,true,false,90,135 +true,true,false,91,135 +true,true,false,111,135 +true,true,false,112,135 +true,true,false,113,180 +true,true,false,114,180 +true,true,false,115,180 +true,true,false,134,180 +true,true,false,135,180 +true,true,false,136,180 +true,true,false,156,180 +true,true,false,157,180 +true,true,false,158,-135 +true,true,false,159,-135 +true,true,false,160,-135 +true,true,false,179,-135 +true,true,false,180,-135 + +true,true,true,-179,180 +true,true,true,-178,180 +true,true,true,-158,180 +true,true,true,-157,180 +true,true,true,-156,180 +true,true,true,-136,180 +true,true,true,-135,180 +true,true,true,-134,180 +true,true,true,-114,180 +true,true,true,-113,180 +true,true,true,-112,-135 +true,true,true,-111,-135 +true,true,true,-110,-135 +true,true,true,-91,-135 +true,true,true,-90,-135 +true,true,true,-89,-135 +true,true,true,-69,-135 +true,true,true,-68,-135 +true,true,true,-67,-90 +true,true,true,-66,-90 +true,true,true,-65,-90 +true,true,true,-46,-90 +true,true,true,-45,-90 +true,true,true,-44,-90 +true,true,true,-24,-90 +true,true,true,-23,-90 +true,true,true,-22,-45 +true,true,true,-21,-45 +true,true,true,-20,-45 +true,true,true,-1,-45 +true,true,true,0,-45 +true,true,true,1,-45 +true,true,true,21,-45 +true,true,true,22,-45 +true,true,true,23,0 +true,true,true,24,0 +true,true,true,25,0 +true,true,true,44,0 +true,true,true,45,0 +true,true,true,46,0 +true,true,true,66,0 +true,true,true,67,0 +true,true,true,68,45 +true,true,true,69,45 +true,true,true,70,45 +true,true,true,89,45 +true,true,true,90,45 +true,true,true,91,45 +true,true,true,111,45 +true,true,true,112,45 +true,true,true,113,90 +true,true,true,114,90 +true,true,true,115,90 +true,true,true,134,90 +true,true,true,135,90 +true,true,true,136,90 +true,true,true,156,90 +true,true,true,157,90 +true,true,true,158,135 +true,true,true,159,135 +true,true,true,160,135 +true,true,true,179,135 +true,true,true,180,135 diff --git a/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nearestFacing.csv b/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nearestFacing.csv new file mode 100644 index 0000000000..090831395e --- /dev/null +++ b/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nearestFacing.csv @@ -0,0 +1,165 @@ +faceEdges,faceVertices,facing,expected + +false,false,-179,90 +false,false,-150,90 +false,false,-120,90 +false,false,-90,90 +false,false,-60,90 +false,false,-30,90 +false,false,0,90 +false,false,30,90 +false,false,60,90 +false,false,90,90 +false,false,120,90 +false,false,150,90 +false,false,180,90 + +true,false,-179,-120 +true,false,-178,-120 +true,false,-151,-120 +true,false,-150,-120 +true,false,-149,-120 +true,false,-121,-120 +true,false,-120,-120 +true,false,-119,-120 +true,false,-91,-120 +true,false,-90,-120 +true,false,-89,-60 +true,false,-61,-60 +true,false,-60,-60 +true,false,-59,-60 +true,false,-31,-60 +true,false,-30,-60 +true,false,-29,0 +true,false,-1,0 +true,false,0,0 +true,false,1,0 +true,false,29,0 +true,false,30,0 +true,false,31,60 +true,false,59,60 +true,false,60,60 +true,false,61,60 +true,false,89,60 +true,false,90,60 +true,false,91,120 +true,false,119,120 +true,false,120,120 +true,false,121,120 +true,false,149,120 +true,false,150,120 +true,false,151,180 +true,false,179,180 +true,false,180,180 + +false,true,-179,-150 +false,true,-178,-150 +false,true,-151,-150 +false,true,-150,-150 +false,true,-149,-150 +false,true,-121,-150 +false,true,-120,-150 +false,true,-119,-90 +false,true,-91,-90 +false,true,-90,-90 +false,true,-89,-90 +false,true,-61,-90 +false,true,-60,-90 +false,true,-59,-30 +false,true,-31,-30 +false,true,-30,-30 +false,true,-29,-30 +false,true,-1,-30 +false,true,0,-30 +false,true,1,30 +false,true,29,30 +false,true,30,30 +false,true,31,30 +false,true,59,30 +false,true,60,30 +false,true,61,90 +false,true,89,90 +false,true,90,90 +false,true,91,90 +false,true,119,90 +false,true,120,90 +false,true,121,150 +false,true,149,150 +false,true,150,150 +false,true,151,150 +false,true,179,150 +false,true,180,150 + +true,true,-179,-150 +true,true,-178,-150 +true,true,-166,-150 +true,true,-165,-150 +true,true,-164,-150 +true,true,-151,-150 +true,true,-150,-150 +true,true,-149,-150 +true,true,-136,-150 +true,true,-135,-150 +true,true,-134,-120 +true,true,-121,-120 +true,true,-120,-120 +true,true,-119,-120 +true,true,-106,-120 +true,true,-105,-120 +true,true,-104,-90 +true,true,-91,-90 +true,true,-90,-90 +true,true,-89,-90 +true,true,-76,-90 +true,true,-75,-90 +true,true,-74,-60 +true,true,-61,-60 +true,true,-60,-60 +true,true,-59,-60 +true,true,-46,-60 +true,true,-45,-60 +true,true,-44,-30 +true,true,-31,-30 +true,true,-30,-30 +true,true,-29,-30 +true,true,-16,-30 +true,true,-15,-30 +true,true,-14,0 +true,true,-1,0 +true,true,0,0 +true,true,1,0 +true,true,14,0 +true,true,15,0 +true,true,16,30 +true,true,29,30 +true,true,30,30 +true,true,31,30 +true,true,44,30 +true,true,45,30 +true,true,46,60 +true,true,59,60 +true,true,60,60 +true,true,61,60 +true,true,74,60 +true,true,75,60 +true,true,76,90 +true,true,89,90 +true,true,90,90 +true,true,91,90 +true,true,104,90 +true,true,105,90 +true,true,106,120 +true,true,119,120 +true,true,120,120 +true,true,121,120 +true,true,134,120 +true,true,135,120 +true,true,136,150 +true,true,149,150 +true,true,150,150 +true,true,151,150 +true,true,164,150 +true,true,165,150 +true,true,166,180 +true,true,179,180 +true,true,180,180 diff --git a/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nextFacing.csv b/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nextFacing.csv new file mode 100644 index 0000000000..5ee0b8c314 --- /dev/null +++ b/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nextFacing.csv @@ -0,0 +1,329 @@ +faceEdges,faceVertices,clockwise,facing,expected + +false,false,false,-179,90 +false,false,false,-150,90 +false,false,false,-120,90 +false,false,false,-90,90 +false,false,false,-60,90 +false,false,false,-30,90 +false,false,false,0,90 +false,false,false,30,90 +false,false,false,60,90 +false,false,false,90,90 +false,false,false,120,90 +false,false,false,150,90 +false,false,false,180,90 + +false,false,true,-179,90 +false,false,true,-150,90 +false,false,true,-120,90 +false,false,true,-90,90 +false,false,true,-60,90 +false,false,true,-30,90 +false,false,true,0,90 +false,false,true,30,90 +false,false,true,60,90 +false,false,true,90,90 +false,false,true,120,90 +false,false,true,150,90 +false,false,true,180,90 + +true,false,false,-179,-60 +true,false,false,-178,-60 +true,false,false,-151,-60 +true,false,false,-150,-60 +true,false,false,-149,-60 +true,false,false,-121,-60 +true,false,false,-120,-60 +true,false,false,-119,-60 +true,false,false,-91,-60 +true,false,false,-90,-60 +true,false,false,-89,0 +true,false,false,-61,0 +true,false,false,-60,0 +true,false,false,-59,0 +true,false,false,-31,0 +true,false,false,-30,0 +true,false,false,-29,60 +true,false,false,-1,60 +true,false,false,0,60 +true,false,false,1,60 +true,false,false,29,60 +true,false,false,30,60 +true,false,false,31,120 +true,false,false,59,120 +true,false,false,60,120 +true,false,false,61,120 +true,false,false,89,120 +true,false,false,90,120 +true,false,false,91,180 +true,false,false,119,180 +true,false,false,120,180 +true,false,false,121,180 +true,false,false,149,180 +true,false,false,150,180 +true,false,false,151,-120 +true,false,false,179,-120 +true,false,false,180,-120 + +true,false,true,-179,180 +true,false,true,-178,180 +true,false,true,-151,180 +true,false,true,-150,180 +true,false,true,-149,180 +true,false,true,-121,180 +true,false,true,-120,180 +true,false,true,-119,180 +true,false,true,-91,180 +true,false,true,-90,180 +true,false,true,-89,-120 +true,false,true,-61,-120 +true,false,true,-60,-120 +true,false,true,-59,-120 +true,false,true,-31,-120 +true,false,true,-30,-120 +true,false,true,-29,-60 +true,false,true,-1,-60 +true,false,true,0,-60 +true,false,true,1,-60 +true,false,true,29,-60 +true,false,true,30,-60 +true,false,true,31,0 +true,false,true,59,0 +true,false,true,60,0 +true,false,true,61,0 +true,false,true,89,0 +true,false,true,90,0 +true,false,true,91,60 +true,false,true,119,60 +true,false,true,120,60 +true,false,true,121,60 +true,false,true,149,60 +true,false,true,150,60 +true,false,true,151,120 +true,false,true,179,120 +true,false,true,180,120 + +false,true,false,-179,-90 +false,true,false,-178,-90 +false,true,false,-151,-90 +false,true,false,-150,-90 +false,true,false,-149,-90 +false,true,false,-121,-90 +false,true,false,-120,-90 +false,true,false,-119,-30 +false,true,false,-91,-30 +false,true,false,-90,-30 +false,true,false,-89,-30 +false,true,false,-61,-30 +false,true,false,-60,-30 +false,true,false,-59,30 +false,true,false,-31,30 +false,true,false,-30,30 +false,true,false,-29,30 +false,true,false,-1,30 +false,true,false,0,30 +false,true,false,1,90 +false,true,false,29,90 +false,true,false,30,90 +false,true,false,31,90 +false,true,false,59,90 +false,true,false,60,90 +false,true,false,61,150 +false,true,false,89,150 +false,true,false,90,150 +false,true,false,91,150 +false,true,false,119,150 +false,true,false,120,150 +false,true,false,121,-150 +false,true,false,149,-150 +false,true,false,150,-150 +false,true,false,151,-150 +false,true,false,179,-150 +false,true,false,180,-150 + +false,true,true,-179,150 +false,true,true,-178,150 +false,true,true,-151,150 +false,true,true,-150,150 +false,true,true,-149,150 +false,true,true,-121,150 +false,true,true,-120,150 +false,true,true,-119,-150 +false,true,true,-91,-150 +false,true,true,-90,-150 +false,true,true,-89,-150 +false,true,true,-61,-150 +false,true,true,-60,-150 +false,true,true,-59,-90 +false,true,true,-31,-90 +false,true,true,-30,-90 +false,true,true,-29,-90 +false,true,true,-1,-90 +false,true,true,0,-90 +false,true,true,1,-30 +false,true,true,29,-30 +false,true,true,30,-30 +false,true,true,31,-30 +false,true,true,59,-30 +false,true,true,60,-30 +false,true,true,61,30 +false,true,true,89,30 +false,true,true,90,30 +false,true,true,91,30 +false,true,true,119,30 +false,true,true,120,30 +false,true,true,121,90 +false,true,true,149,90 +false,true,true,150,90 +false,true,true,151,90 +false,true,true,179,90 +false,true,true,180,90 + +true,true,false,-179,-120 +true,true,false,-178,-120 +true,true,false,-166,-120 +true,true,false,-165,-120 +true,true,false,-164,-120 +true,true,false,-151,-120 +true,true,false,-150,-120 +true,true,false,-149,-120 +true,true,false,-136,-120 +true,true,false,-135,-120 +true,true,false,-134,-90 +true,true,false,-121,-90 +true,true,false,-120,-90 +true,true,false,-119,-90 +true,true,false,-106,-90 +true,true,false,-105,-90 +true,true,false,-104,-60 +true,true,false,-91,-60 +true,true,false,-90,-60 +true,true,false,-89,-60 +true,true,false,-76,-60 +true,true,false,-75,-60 +true,true,false,-74,-30 +true,true,false,-61,-30 +true,true,false,-60,-30 +true,true,false,-59,-30 +true,true,false,-46,-30 +true,true,false,-45,-30 +true,true,false,-44,0 +true,true,false,-31,0 +true,true,false,-30,0 +true,true,false,-29,0 +true,true,false,-16,0 +true,true,false,-15,0 +true,true,false,-14,30 +true,true,false,-1,30 +true,true,false,0,30 +true,true,false,1,30 +true,true,false,14,30 +true,true,false,15,30 +true,true,false,16,60 +true,true,false,29,60 +true,true,false,30,60 +true,true,false,31,60 +true,true,false,44,60 +true,true,false,45,60 +true,true,false,46,90 +true,true,false,59,90 +true,true,false,60,90 +true,true,false,61,90 +true,true,false,74,90 +true,true,false,75,90 +true,true,false,76,120 +true,true,false,89,120 +true,true,false,90,120 +true,true,false,91,120 +true,true,false,104,120 +true,true,false,105,120 +true,true,false,106,150 +true,true,false,119,150 +true,true,false,120,150 +true,true,false,121,150 +true,true,false,134,150 +true,true,false,135,150 +true,true,false,136,180 +true,true,false,149,180 +true,true,false,150,180 +true,true,false,151,180 +true,true,false,164,180 +true,true,false,165,180 +true,true,false,166,-150 +true,true,false,179,-150 +true,true,false,180,-150 + +true,true,true,-179,180 +true,true,true,-178,180 +true,true,true,-166,180 +true,true,true,-165,180 +true,true,true,-164,180 +true,true,true,-151,180 +true,true,true,-150,180 +true,true,true,-149,180 +true,true,true,-136,180 +true,true,true,-135,180 +true,true,true,-134,-150 +true,true,true,-121,-150 +true,true,true,-120,-150 +true,true,true,-119,-150 +true,true,true,-106,-150 +true,true,true,-105,-150 +true,true,true,-104,-120 +true,true,true,-91,-120 +true,true,true,-90,-120 +true,true,true,-89,-120 +true,true,true,-76,-120 +true,true,true,-75,-120 +true,true,true,-74,-90 +true,true,true,-61,-90 +true,true,true,-60,-90 +true,true,true,-59,-90 +true,true,true,-46,-90 +true,true,true,-45,-90 +true,true,true,-44,-60 +true,true,true,-31,-60 +true,true,true,-30,-60 +true,true,true,-29,-60 +true,true,true,-16,-60 +true,true,true,-15,-60 +true,true,true,-14,-30 +true,true,true,-1,-30 +true,true,true,0,-30 +true,true,true,1,-30 +true,true,true,14,-30 +true,true,true,15,-30 +true,true,true,16,0 +true,true,true,29,0 +true,true,true,30,0 +true,true,true,31,0 +true,true,true,44,0 +true,true,true,45,0 +true,true,true,46,30 +true,true,true,59,30 +true,true,true,60,30 +true,true,true,61,30 +true,true,true,74,30 +true,true,true,75,30 +true,true,true,76,60 +true,true,true,89,60 +true,true,true,90,60 +true,true,true,91,60 +true,true,true,104,60 +true,true,true,105,60 +true,true,true,106,90 +true,true,true,119,90 +true,true,true,120,90 +true,true,true,121,90 +true,true,true,134,90 +true,true,true,135,90 +true,true,true,136,120 +true,true,true,149,120 +true,true,true,150,120 +true,true,true,151,120 +true,true,true,164,120 +true,true,true,165,120 +true,true,true,166,150 +true,true,true,179,150 +true,true,true,180,150 diff --git a/src/test/resources/net/rptools/maptool/model/HexGridVertical.nearestFacing.csv b/src/test/resources/net/rptools/maptool/model/HexGridVertical.nearestFacing.csv new file mode 100644 index 0000000000..52a5cbc3e5 --- /dev/null +++ b/src/test/resources/net/rptools/maptool/model/HexGridVertical.nearestFacing.csv @@ -0,0 +1,165 @@ +faceEdges,faceVertices,facing,expected + +false,false,-179,90 +false,false,-150,90 +false,false,-120,90 +false,false,-90,90 +false,false,-60,90 +false,false,-30,90 +false,false,0,90 +false,false,30,90 +false,false,60,90 +false,false,90,90 +false,false,120,90 +false,false,150,90 +false,false,180,90 + +true,false,-179,-150 +true,false,-178,-150 +true,false,-151,-150 +true,false,-150,-150 +true,false,-149,-150 +true,false,-121,-150 +true,false,-120,-150 +true,false,-119,-90 +true,false,-91,-90 +true,false,-90,-90 +true,false,-89,-90 +true,false,-61,-90 +true,false,-60,-90 +true,false,-59,-30 +true,false,-31,-30 +true,false,-30,-30 +true,false,-29,-30 +true,false,-1,-30 +true,false,0,-30 +true,false,1,30 +true,false,29,30 +true,false,30,30 +true,false,31,30 +true,false,59,30 +true,false,60,30 +true,false,61,90 +true,false,89,90 +true,false,90,90 +true,false,91,90 +true,false,119,90 +true,false,120,90 +true,false,121,150 +true,false,149,150 +true,false,150,150 +true,false,151,150 +true,false,179,150 +true,false,180,150 + +false,true,-179,-120 +false,true,-178,-120 +false,true,-151,-120 +false,true,-150,-120 +false,true,-149,-120 +false,true,-121,-120 +false,true,-120,-120 +false,true,-119,-120 +false,true,-91,-120 +false,true,-90,-120 +false,true,-89,-60 +false,true,-61,-60 +false,true,-60,-60 +false,true,-59,-60 +false,true,-31,-60 +false,true,-30,-60 +false,true,-29,0 +false,true,-1,0 +false,true,0,0 +false,true,1,0 +false,true,29,0 +false,true,30,0 +false,true,31,60 +false,true,59,60 +false,true,60,60 +false,true,61,60 +false,true,89,60 +false,true,90,60 +false,true,91,120 +false,true,119,120 +false,true,120,120 +false,true,121,120 +false,true,149,120 +false,true,150,120 +false,true,151,180 +false,true,179,180 +false,true,180,180 + +true,true,-179,-150 +true,true,-178,-150 +true,true,-166,-150 +true,true,-165,-150 +true,true,-164,-150 +true,true,-151,-150 +true,true,-150,-150 +true,true,-149,-150 +true,true,-136,-150 +true,true,-135,-150 +true,true,-134,-120 +true,true,-121,-120 +true,true,-120,-120 +true,true,-119,-120 +true,true,-106,-120 +true,true,-105,-120 +true,true,-104,-90 +true,true,-91,-90 +true,true,-90,-90 +true,true,-89,-90 +true,true,-76,-90 +true,true,-75,-90 +true,true,-74,-60 +true,true,-61,-60 +true,true,-60,-60 +true,true,-59,-60 +true,true,-46,-60 +true,true,-45,-60 +true,true,-44,-30 +true,true,-31,-30 +true,true,-30,-30 +true,true,-29,-30 +true,true,-16,-30 +true,true,-15,-30 +true,true,-14,0 +true,true,-1,0 +true,true,0,0 +true,true,1,0 +true,true,14,0 +true,true,15,0 +true,true,16,30 +true,true,29,30 +true,true,30,30 +true,true,31,30 +true,true,44,30 +true,true,45,30 +true,true,46,60 +true,true,59,60 +true,true,60,60 +true,true,61,60 +true,true,74,60 +true,true,75,60 +true,true,76,90 +true,true,89,90 +true,true,90,90 +true,true,91,90 +true,true,104,90 +true,true,105,90 +true,true,106,120 +true,true,119,120 +true,true,120,120 +true,true,121,120 +true,true,134,120 +true,true,135,120 +true,true,136,150 +true,true,149,150 +true,true,150,150 +true,true,151,150 +true,true,164,150 +true,true,165,150 +true,true,166,180 +true,true,179,180 +true,true,180,180 diff --git a/src/test/resources/net/rptools/maptool/model/HexGridVertical.nextFacing.csv b/src/test/resources/net/rptools/maptool/model/HexGridVertical.nextFacing.csv new file mode 100644 index 0000000000..342ba1306b --- /dev/null +++ b/src/test/resources/net/rptools/maptool/model/HexGridVertical.nextFacing.csv @@ -0,0 +1,329 @@ +faceEdges,faceVertices,clockwise,facing,expected + +false,false,false,-179,90 +false,false,false,-150,90 +false,false,false,-120,90 +false,false,false,-90,90 +false,false,false,-60,90 +false,false,false,-30,90 +false,false,false,0,90 +false,false,false,30,90 +false,false,false,60,90 +false,false,false,90,90 +false,false,false,120,90 +false,false,false,150,90 +false,false,false,180,90 + +false,false,true,-179,90 +false,false,true,-150,90 +false,false,true,-120,90 +false,false,true,-90,90 +false,false,true,-60,90 +false,false,true,-30,90 +false,false,true,0,90 +false,false,true,30,90 +false,false,true,60,90 +false,false,true,90,90 +false,false,true,120,90 +false,false,true,150,90 +false,false,true,180,90 + +false,true,false,-179,-60 +false,true,false,-178,-60 +false,true,false,-151,-60 +false,true,false,-150,-60 +false,true,false,-149,-60 +false,true,false,-121,-60 +false,true,false,-120,-60 +false,true,false,-119,-60 +false,true,false,-91,-60 +false,true,false,-90,-60 +false,true,false,-89,0 +false,true,false,-61,0 +false,true,false,-60,0 +false,true,false,-59,0 +false,true,false,-31,0 +false,true,false,-30,0 +false,true,false,-29,60 +false,true,false,-1,60 +false,true,false,0,60 +false,true,false,1,60 +false,true,false,29,60 +false,true,false,30,60 +false,true,false,31,120 +false,true,false,59,120 +false,true,false,60,120 +false,true,false,61,120 +false,true,false,89,120 +false,true,false,90,120 +false,true,false,91,180 +false,true,false,119,180 +false,true,false,120,180 +false,true,false,121,180 +false,true,false,149,180 +false,true,false,150,180 +false,true,false,151,-120 +false,true,false,179,-120 +false,true,false,180,-120 + +false,true,true,-179,180 +false,true,true,-178,180 +false,true,true,-151,180 +false,true,true,-150,180 +false,true,true,-149,180 +false,true,true,-121,180 +false,true,true,-120,180 +false,true,true,-119,180 +false,true,true,-91,180 +false,true,true,-90,180 +false,true,true,-89,-120 +false,true,true,-61,-120 +false,true,true,-60,-120 +false,true,true,-59,-120 +false,true,true,-31,-120 +false,true,true,-30,-120 +false,true,true,-29,-60 +false,true,true,-1,-60 +false,true,true,0,-60 +false,true,true,1,-60 +false,true,true,29,-60 +false,true,true,30,-60 +false,true,true,31,0 +false,true,true,59,0 +false,true,true,60,0 +false,true,true,61,0 +false,true,true,89,0 +false,true,true,90,0 +false,true,true,91,60 +false,true,true,119,60 +false,true,true,120,60 +false,true,true,121,60 +false,true,true,149,60 +false,true,true,150,60 +false,true,true,151,120 +false,true,true,179,120 +false,true,true,180,120 + +true,false,false,-179,-90 +true,false,false,-178,-90 +true,false,false,-151,-90 +true,false,false,-150,-90 +true,false,false,-149,-90 +true,false,false,-121,-90 +true,false,false,-120,-90 +true,false,false,-119,-30 +true,false,false,-91,-30 +true,false,false,-90,-30 +true,false,false,-89,-30 +true,false,false,-61,-30 +true,false,false,-60,-30 +true,false,false,-59,30 +true,false,false,-31,30 +true,false,false,-30,30 +true,false,false,-29,30 +true,false,false,-1,30 +true,false,false,0,30 +true,false,false,1,90 +true,false,false,29,90 +true,false,false,30,90 +true,false,false,31,90 +true,false,false,59,90 +true,false,false,60,90 +true,false,false,61,150 +true,false,false,89,150 +true,false,false,90,150 +true,false,false,91,150 +true,false,false,119,150 +true,false,false,120,150 +true,false,false,121,-150 +true,false,false,149,-150 +true,false,false,150,-150 +true,false,false,151,-150 +true,false,false,179,-150 +true,false,false,180,-150 + +true,false,true,-179,150 +true,false,true,-178,150 +true,false,true,-151,150 +true,false,true,-150,150 +true,false,true,-149,150 +true,false,true,-121,150 +true,false,true,-120,150 +true,false,true,-119,-150 +true,false,true,-91,-150 +true,false,true,-90,-150 +true,false,true,-89,-150 +true,false,true,-61,-150 +true,false,true,-60,-150 +true,false,true,-59,-90 +true,false,true,-31,-90 +true,false,true,-30,-90 +true,false,true,-29,-90 +true,false,true,-1,-90 +true,false,true,0,-90 +true,false,true,1,-30 +true,false,true,29,-30 +true,false,true,30,-30 +true,false,true,31,-30 +true,false,true,59,-30 +true,false,true,60,-30 +true,false,true,61,30 +true,false,true,89,30 +true,false,true,90,30 +true,false,true,91,30 +true,false,true,119,30 +true,false,true,120,30 +true,false,true,121,90 +true,false,true,149,90 +true,false,true,150,90 +true,false,true,151,90 +true,false,true,179,90 +true,false,true,180,90 + +true,true,false,-179,-120 +true,true,false,-178,-120 +true,true,false,-166,-120 +true,true,false,-165,-120 +true,true,false,-164,-120 +true,true,false,-151,-120 +true,true,false,-150,-120 +true,true,false,-149,-120 +true,true,false,-136,-120 +true,true,false,-135,-120 +true,true,false,-134,-90 +true,true,false,-121,-90 +true,true,false,-120,-90 +true,true,false,-119,-90 +true,true,false,-106,-90 +true,true,false,-105,-90 +true,true,false,-104,-60 +true,true,false,-91,-60 +true,true,false,-90,-60 +true,true,false,-89,-60 +true,true,false,-76,-60 +true,true,false,-75,-60 +true,true,false,-74,-30 +true,true,false,-61,-30 +true,true,false,-60,-30 +true,true,false,-59,-30 +true,true,false,-46,-30 +true,true,false,-45,-30 +true,true,false,-44,0 +true,true,false,-31,0 +true,true,false,-30,0 +true,true,false,-29,0 +true,true,false,-16,0 +true,true,false,-15,0 +true,true,false,-14,30 +true,true,false,-1,30 +true,true,false,0,30 +true,true,false,1,30 +true,true,false,14,30 +true,true,false,15,30 +true,true,false,16,60 +true,true,false,29,60 +true,true,false,30,60 +true,true,false,31,60 +true,true,false,44,60 +true,true,false,45,60 +true,true,false,46,90 +true,true,false,59,90 +true,true,false,60,90 +true,true,false,61,90 +true,true,false,74,90 +true,true,false,75,90 +true,true,false,76,120 +true,true,false,89,120 +true,true,false,90,120 +true,true,false,91,120 +true,true,false,104,120 +true,true,false,105,120 +true,true,false,106,150 +true,true,false,119,150 +true,true,false,120,150 +true,true,false,121,150 +true,true,false,134,150 +true,true,false,135,150 +true,true,false,136,180 +true,true,false,149,180 +true,true,false,150,180 +true,true,false,151,180 +true,true,false,164,180 +true,true,false,165,180 +true,true,false,166,-150 +true,true,false,179,-150 +true,true,false,180,-150 + +true,true,true,-179,180 +true,true,true,-178,180 +true,true,true,-166,180 +true,true,true,-165,180 +true,true,true,-164,180 +true,true,true,-151,180 +true,true,true,-150,180 +true,true,true,-149,180 +true,true,true,-136,180 +true,true,true,-135,180 +true,true,true,-134,-150 +true,true,true,-121,-150 +true,true,true,-120,-150 +true,true,true,-119,-150 +true,true,true,-106,-150 +true,true,true,-105,-150 +true,true,true,-104,-120 +true,true,true,-91,-120 +true,true,true,-90,-120 +true,true,true,-89,-120 +true,true,true,-76,-120 +true,true,true,-75,-120 +true,true,true,-74,-90 +true,true,true,-61,-90 +true,true,true,-60,-90 +true,true,true,-59,-90 +true,true,true,-46,-90 +true,true,true,-45,-90 +true,true,true,-44,-60 +true,true,true,-31,-60 +true,true,true,-30,-60 +true,true,true,-29,-60 +true,true,true,-16,-60 +true,true,true,-15,-60 +true,true,true,-14,-30 +true,true,true,-1,-30 +true,true,true,0,-30 +true,true,true,1,-30 +true,true,true,14,-30 +true,true,true,15,-30 +true,true,true,16,0 +true,true,true,29,0 +true,true,true,30,0 +true,true,true,31,0 +true,true,true,44,0 +true,true,true,45,0 +true,true,true,46,30 +true,true,true,59,30 +true,true,true,60,30 +true,true,true,61,30 +true,true,true,74,30 +true,true,true,75,30 +true,true,true,76,60 +true,true,true,89,60 +true,true,true,90,60 +true,true,true,91,60 +true,true,true,104,60 +true,true,true,105,60 +true,true,true,106,90 +true,true,true,119,90 +true,true,true,120,90 +true,true,true,121,90 +true,true,true,134,90 +true,true,true,135,90 +true,true,true,136,120 +true,true,true,149,120 +true,true,true,150,120 +true,true,true,151,120 +true,true,true,164,120 +true,true,true,165,120 +true,true,true,166,150 +true,true,true,179,150 +true,true,true,180,150 diff --git a/src/test/resources/net/rptools/maptool/model/IsometricGrid.nearestFacing.csv b/src/test/resources/net/rptools/maptool/model/IsometricGrid.nearestFacing.csv new file mode 100644 index 0000000000..70e65f7eb0 --- /dev/null +++ b/src/test/resources/net/rptools/maptool/model/IsometricGrid.nearestFacing.csv @@ -0,0 +1,195 @@ +faceEdges,faceVertices,facing,expected + +false,false,-179,90 +false,false,-178,90 +false,false,-158,90 +false,false,-157,90 +false,false,-156,90 +false,false,-136,90 +false,false,-135,90 +false,false,-134,90 +false,false,-114,90 +false,false,-113,90 +false,false,-112,90 +false,false,-111,90 +false,false,-110,90 +false,false,-91,90 +false,false,-90,90 +false,false,-89,90 +false,false,-69,90 +false,false,-68,90 +false,false,-67,90 +false,false,-66,90 +false,false,-65,90 +false,false,-46,90 +false,false,-45,90 +false,false,-44,90 +false,false,-24,90 +false,false,-23,90 +false,false,-22,90 +false,false,-21,90 +false,false,-20,90 +false,false,-1,90 +false,false,0,90 +false,false,1,90 +false,false,21,90 +false,false,22,90 +false,false,23,90 +false,false,24,90 +false,false,25,90 +false,false,44,90 +false,false,45,90 +false,false,46,90 +false,false,66,90 +false,false,67,90 +false,false,68,90 +false,false,69,90 +false,false,70,90 +false,false,89,90 +false,false,90,90 +false,false,91,90 +false,false,111,90 +false,false,112,90 +false,false,113,90 +false,false,114,90 +false,false,115,90 +false,false,134,90 +false,false,135,90 +false,false,136,90 +false,false,156,90 +false,false,157,90 +false,false,158,90 +false,false,159,90 +false,false,160,90 +false,false,179,90 +false,false,180,90 + +true,false,-179,-135 +true,false,-178,-135 +true,false,-136,-135 +true,false,-135,-135 +true,false,-134,-135 +true,false,-91,-135 +true,false,-90,-135 +true,false,-89,-45 +true,false,-88,-45 +true,false,-87,-45 +true,false,-46,-45 +true,false,-45,-45 +true,false,-44,-45 +true,false,-2,-45 +true,false,-1,-45 +true,false,0,-45 +true,false,1,45 +true,false,2,45 +true,false,44,45 +true,false,45,45 +true,false,46,45 +true,false,89,45 +true,false,90,45 +true,false,91,135 +true,false,92,135 +true,false,93,135 +true,false,134,135 +true,false,135,135 +true,false,136,135 +true,false,179,135 +true,false,180,135 + +false,true,-179,-90 +false,true,-178,-90 +false,true,-136,-90 +false,true,-135,-90 +false,true,-134,-90 +false,true,-133,-90 +false,true,-132,-90 +false,true,-91,-90 +false,true,-90,-90 +false,true,-89,-90 +false,true,-47,-90 +false,true,-46,-90 +false,true,-45,-90 +false,true,-44,0 +false,true,-43,0 +false,true,-1,0 +false,true,0,0 +false,true,1,0 +false,true,43,0 +false,true,44,0 +false,true,45,0 +false,true,46,90 +false,true,47,90 +false,true,89,90 +false,true,90,90 +false,true,91,90 +false,true,134,90 +false,true,135,90 +false,true,136,180 +false,true,137,180 +false,true,138,180 +false,true,179,180 +false,true,180,180 + +true,true,-179,-135 +true,true,-178,-135 +true,true,-158,-135 +true,true,-157,-135 +true,true,-156,-135 +true,true,-136,-135 +true,true,-135,-135 +true,true,-134,-135 +true,true,-114,-135 +true,true,-113,-135 +true,true,-112,-90 +true,true,-111,-90 +true,true,-110,-90 +true,true,-91,-90 +true,true,-90,-90 +true,true,-89,-90 +true,true,-69,-90 +true,true,-68,-90 +true,true,-67,-45 +true,true,-66,-45 +true,true,-65,-45 +true,true,-46,-45 +true,true,-45,-45 +true,true,-44,-45 +true,true,-24,-45 +true,true,-23,-45 +true,true,-22,0 +true,true,-21,0 +true,true,-20,0 +true,true,-1,0 +true,true,0,0 +true,true,1,0 +true,true,21,0 +true,true,22,0 +true,true,23,45 +true,true,24,45 +true,true,25,45 +true,true,44,45 +true,true,45,45 +true,true,46,45 +true,true,66,45 +true,true,67,45 +true,true,68,90 +true,true,69,90 +true,true,70,90 +true,true,89,90 +true,true,90,90 +true,true,91,90 +true,true,111,90 +true,true,112,90 +true,true,113,135 +true,true,114,135 +true,true,115,135 +true,true,134,135 +true,true,135,135 +true,true,136,135 +true,true,156,135 +true,true,157,135 +true,true,158,180 +true,true,159,180 +true,true,160,180 +true,true,179,180 +true,true,180,180 diff --git a/src/test/resources/net/rptools/maptool/model/IsometricGrid.nextFacing.csv b/src/test/resources/net/rptools/maptool/model/IsometricGrid.nextFacing.csv new file mode 100644 index 0000000000..2775fd4620 --- /dev/null +++ b/src/test/resources/net/rptools/maptool/model/IsometricGrid.nextFacing.csv @@ -0,0 +1,380 @@ +faceEdges,faceVertices,clockwise,facing,expected + +false,false,false,-179,90 +false,false,false,-178,90 +false,false,false,-158,90 +false,false,false,-157,90 +false,false,false,-156,90 +false,false,false,-136,90 +false,false,false,-135,90 +false,false,false,-134,90 +false,false,false,-114,90 +false,false,false,-113,90 +false,false,false,-112,90 +false,false,false,-111,90 +false,false,false,-110,90 +false,false,false,-91,90 +false,false,false,-90,90 +false,false,false,-89,90 +false,false,false,-69,90 +false,false,false,-68,90 +false,false,false,-67,90 +false,false,false,-66,90 +false,false,false,-65,90 +false,false,false,-46,90 +false,false,false,-45,90 +false,false,false,-44,90 +false,false,false,-24,90 +false,false,false,-23,90 +false,false,false,-22,90 +false,false,false,-21,90 +false,false,false,-20,90 +false,false,false,-1,90 +false,false,false,0,90 +false,false,false,1,90 +false,false,false,21,90 +false,false,false,22,90 +false,false,false,23,90 +false,false,false,24,90 +false,false,false,25,90 +false,false,false,44,90 +false,false,false,45,90 +false,false,false,46,90 +false,false,false,66,90 +false,false,false,67,90 +false,false,false,68,90 +false,false,false,69,90 +false,false,false,70,90 +false,false,false,89,90 +false,false,false,90,90 +false,false,false,91,90 +false,false,false,111,90 +false,false,false,112,90 +false,false,false,113,90 +false,false,false,114,90 +false,false,false,115,90 +false,false,false,134,90 +false,false,false,135,90 +false,false,false,136,90 +false,false,false,156,90 +false,false,false,157,90 +false,false,false,158,90 +false,false,false,159,90 +false,false,false,160,90 +false,false,false,179,90 +false,false,false,180,90 + +false,false,true,-179,90 +false,false,true,-178,90 +false,false,true,-158,90 +false,false,true,-157,90 +false,false,true,-156,90 +false,false,true,-136,90 +false,false,true,-135,90 +false,false,true,-134,90 +false,false,true,-114,90 +false,false,true,-113,90 +false,false,true,-112,90 +false,false,true,-111,90 +false,false,true,-110,90 +false,false,true,-91,90 +false,false,true,-90,90 +false,false,true,-89,90 +false,false,true,-69,90 +false,false,true,-68,90 +false,false,true,-67,90 +false,false,true,-66,90 +false,false,true,-65,90 +false,false,true,-46,90 +false,false,true,-45,90 +false,false,true,-44,90 +false,false,true,-24,90 +false,false,true,-23,90 +false,false,true,-22,90 +false,false,true,-21,90 +false,false,true,-20,90 +false,false,true,-1,90 +false,false,true,0,90 +false,false,true,1,90 +false,false,true,21,90 +false,false,true,22,90 +false,false,true,23,90 +false,false,true,24,90 +false,false,true,25,90 +false,false,true,44,90 +false,false,true,45,90 +false,false,true,46,90 +false,false,true,66,90 +false,false,true,67,90 +false,false,true,68,90 +false,false,true,69,90 +false,false,true,70,90 +false,false,true,89,90 +false,false,true,90,90 +false,false,true,91,90 +false,false,true,111,90 +false,false,true,112,90 +false,false,true,113,90 +false,false,true,114,90 +false,false,true,115,90 +false,false,true,134,90 +false,false,true,135,90 +false,false,true,136,90 +false,false,true,156,90 +false,false,true,157,90 +false,false,true,158,90 +false,false,true,159,90 +false,false,true,160,90 +false,false,true,179,90 +false,false,true,180,90 + +true,false,false,-179,-45 +true,false,false,-178,-45 +true,false,false,-136,-45 +true,false,false,-135,-45 +true,false,false,-134,-45 +true,false,false,-91,-45 +true,false,false,-90,-45 +true,false,false,-89,45 +true,false,false,-88,45 +true,false,false,-87,45 +true,false,false,-46,45 +true,false,false,-45,45 +true,false,false,-44,45 +true,false,false,-2,45 +true,false,false,-1,45 +true,false,false,0,45 +true,false,false,1,135 +true,false,false,2,135 +true,false,false,44,135 +true,false,false,45,135 +true,false,false,46,135 +true,false,false,89,135 +true,false,false,90,135 +true,false,false,91,-135 +true,false,false,92,-135 +true,false,false,93,-135 +true,false,false,134,-135 +true,false,false,135,-135 +true,false,false,136,-135 +true,false,false,179,-135 +true,false,false,180,-135 + +true,false,true,-179,135 +true,false,true,-178,135 +true,false,true,-136,135 +true,false,true,-135,135 +true,false,true,-134,135 +true,false,true,-91,135 +true,false,true,-90,135 +true,false,true,-89,-135 +true,false,true,-88,-135 +true,false,true,-87,-135 +true,false,true,-46,-135 +true,false,true,-45,-135 +true,false,true,-44,-135 +true,false,true,-2,-135 +true,false,true,-1,-135 +true,false,true,0,-135 +true,false,true,1,-45 +true,false,true,2,-45 +true,false,true,44,-45 +true,false,true,45,-45 +true,false,true,46,-45 +true,false,true,89,-45 +true,false,true,90,-45 +true,false,true,91,45 +true,false,true,92,45 +true,false,true,93,45 +true,false,true,134,45 +true,false,true,135,45 +true,false,true,136,45 +true,false,true,179,45 +true,false,true,180,45 + +false,true,false,-179,0 +false,true,false,-178,0 +false,true,false,-136,0 +false,true,false,-135,0 +false,true,false,-134,0 +false,true,false,-133,0 +false,true,false,-132,0 +false,true,false,-46,0 +false,true,false,-45,0 +false,true,false,-44,90 +false,true,false,-43,90 +false,true,false,-42,90 +false,true,false,44,90 +false,true,false,45,90 +false,true,false,46,180 +false,true,false,47,180 +false,true,false,48,180 +false,true,false,134,180 +false,true,false,135,180 +false,true,false,136,-90 +false,true,false,137,-90 +false,true,false,138,-90 +false,true,false,179,-90 +false,true,false,180,-90 + +false,true,true,-179,180 +false,true,true,-178,180 +false,true,true,-136,180 +false,true,true,-135,180 +false,true,true,-134,180 +false,true,true,-133,180 +false,true,true,-132,180 +false,true,true,-91,180 +false,true,true,-90,180 +false,true,true,-89,180 +false,true,true,-46,180 +false,true,true,-45,180 +false,true,true,-44,-90 +false,true,true,-43,-90 +false,true,true,-42,-90 +false,true,true,-1,-90 +false,true,true,0,-90 +false,true,true,1,-90 +false,true,true,44,-90 +false,true,true,45,-90 +false,true,true,46,0 +false,true,true,47,0 +false,true,true,48,0 +false,true,true,89,0 +false,true,true,90,0 +false,true,true,91,0 +false,true,true,134,0 +false,true,true,135,0 +false,true,true,136,90 +false,true,true,137,90 +false,true,true,138,90 +false,true,true,179,90 +false,true,true,180,90 + +true,true,false,-179,-90 +true,true,false,-178,-90 +true,true,false,-158,-90 +true,true,false,-157,-90 +true,true,false,-156,-90 +true,true,false,-136,-90 +true,true,false,-135,-90 +true,true,false,-134,-90 +true,true,false,-114,-90 +true,true,false,-113,-90 +true,true,false,-112,-45 +true,true,false,-111,-45 +true,true,false,-110,-45 +true,true,false,-91,-45 +true,true,false,-90,-45 +true,true,false,-89,-45 +true,true,false,-69,-45 +true,true,false,-68,-45 +true,true,false,-67,0 +true,true,false,-66,0 +true,true,false,-65,0 +true,true,false,-46,0 +true,true,false,-45,0 +true,true,false,-44,0 +true,true,false,-24,0 +true,true,false,-23,0 +true,true,false,-22,45 +true,true,false,-21,45 +true,true,false,-20,45 +true,true,false,-1,45 +true,true,false,0,45 +true,true,false,1,45 +true,true,false,21,45 +true,true,false,22,45 +true,true,false,23,90 +true,true,false,24,90 +true,true,false,25,90 +true,true,false,44,90 +true,true,false,45,90 +true,true,false,46,90 +true,true,false,66,90 +true,true,false,67,90 +true,true,false,68,135 +true,true,false,69,135 +true,true,false,70,135 +true,true,false,89,135 +true,true,false,90,135 +true,true,false,91,135 +true,true,false,111,135 +true,true,false,112,135 +true,true,false,113,180 +true,true,false,114,180 +true,true,false,115,180 +true,true,false,134,180 +true,true,false,135,180 +true,true,false,136,180 +true,true,false,156,180 +true,true,false,157,180 +true,true,false,158,-135 +true,true,false,159,-135 +true,true,false,160,-135 +true,true,false,179,-135 +true,true,false,180,-135 + +true,true,true,-179,180 +true,true,true,-178,180 +true,true,true,-158,180 +true,true,true,-157,180 +true,true,true,-156,180 +true,true,true,-136,180 +true,true,true,-135,180 +true,true,true,-134,180 +true,true,true,-114,180 +true,true,true,-113,180 +true,true,true,-112,-135 +true,true,true,-111,-135 +true,true,true,-110,-135 +true,true,true,-91,-135 +true,true,true,-90,-135 +true,true,true,-89,-135 +true,true,true,-69,-135 +true,true,true,-68,-135 +true,true,true,-67,-90 +true,true,true,-66,-90 +true,true,true,-65,-90 +true,true,true,-46,-90 +true,true,true,-45,-90 +true,true,true,-44,-90 +true,true,true,-24,-90 +true,true,true,-23,-90 +true,true,true,-22,-45 +true,true,true,-21,-45 +true,true,true,-20,-45 +true,true,true,-1,-45 +true,true,true,0,-45 +true,true,true,1,-45 +true,true,true,21,-45 +true,true,true,22,-45 +true,true,true,23,0 +true,true,true,24,0 +true,true,true,25,0 +true,true,true,44,0 +true,true,true,45,0 +true,true,true,46,0 +true,true,true,66,0 +true,true,true,67,0 +true,true,true,68,45 +true,true,true,69,45 +true,true,true,70,45 +true,true,true,89,45 +true,true,true,90,45 +true,true,true,91,45 +true,true,true,111,45 +true,true,true,112,45 +true,true,true,113,90 +true,true,true,114,90 +true,true,true,115,90 +true,true,true,134,90 +true,true,true,135,90 +true,true,true,136,90 +true,true,true,156,90 +true,true,true,157,90 +true,true,true,158,135 +true,true,true,159,135 +true,true,true,160,135 +true,true,true,179,135 +true,true,true,180,135 diff --git a/src/test/resources/net/rptools/maptool/model/SquareGrid.nearestFacing.csv b/src/test/resources/net/rptools/maptool/model/SquareGrid.nearestFacing.csv new file mode 100644 index 0000000000..37acc1d034 --- /dev/null +++ b/src/test/resources/net/rptools/maptool/model/SquareGrid.nearestFacing.csv @@ -0,0 +1,195 @@ +faceEdges,faceVertices,facing,expected + +false,false,-179,90 +false,false,-178,90 +false,false,-158,90 +false,false,-157,90 +false,false,-156,90 +false,false,-136,90 +false,false,-135,90 +false,false,-134,90 +false,false,-114,90 +false,false,-113,90 +false,false,-112,90 +false,false,-111,90 +false,false,-110,90 +false,false,-91,90 +false,false,-90,90 +false,false,-89,90 +false,false,-69,90 +false,false,-68,90 +false,false,-67,90 +false,false,-66,90 +false,false,-65,90 +false,false,-46,90 +false,false,-45,90 +false,false,-44,90 +false,false,-24,90 +false,false,-23,90 +false,false,-22,90 +false,false,-21,90 +false,false,-20,90 +false,false,-1,90 +false,false,0,90 +false,false,1,90 +false,false,21,90 +false,false,22,90 +false,false,23,90 +false,false,24,90 +false,false,25,90 +false,false,44,90 +false,false,45,90 +false,false,46,90 +false,false,66,90 +false,false,67,90 +false,false,68,90 +false,false,69,90 +false,false,70,90 +false,false,89,90 +false,false,90,90 +false,false,91,90 +false,false,111,90 +false,false,112,90 +false,false,113,90 +false,false,114,90 +false,false,115,90 +false,false,134,90 +false,false,135,90 +false,false,136,90 +false,false,156,90 +false,false,157,90 +false,false,158,90 +false,false,159,90 +false,false,160,90 +false,false,179,90 +false,false,180,90 + +true,false,-179,-90 +true,false,-178,-90 +true,false,-136,-90 +true,false,-135,-90 +true,false,-134,-90 +true,false,-133,-90 +true,false,-132,-90 +true,false,-91,-90 +true,false,-90,-90 +true,false,-89,-90 +true,false,-47,-90 +true,false,-46,-90 +true,false,-45,-90 +true,false,-44,0 +true,false,-43,0 +true,false,-1,0 +true,false,0,0 +true,false,1,0 +true,false,43,0 +true,false,44,0 +true,false,45,0 +true,false,46,90 +true,false,47,90 +true,false,89,90 +true,false,90,90 +true,false,91,90 +true,false,134,90 +true,false,135,90 +true,false,136,180 +true,false,137,180 +true,false,138,180 +true,false,179,180 +true,false,180,180 + +false,true,-179,-135 +false,true,-178,-135 +false,true,-136,-135 +false,true,-135,-135 +false,true,-134,-135 +false,true,-91,-135 +false,true,-90,-135 +false,true,-89,-45 +false,true,-88,-45 +false,true,-87,-45 +false,true,-46,-45 +false,true,-45,-45 +false,true,-44,-45 +false,true,-2,-45 +false,true,-1,-45 +false,true,0,-45 +false,true,1,45 +false,true,2,45 +false,true,44,45 +false,true,45,45 +false,true,46,45 +false,true,89,45 +false,true,90,45 +false,true,91,135 +false,true,92,135 +false,true,93,135 +false,true,134,135 +false,true,135,135 +false,true,136,135 +false,true,179,135 +false,true,180,135 + +true,true,-179,-135 +true,true,-178,-135 +true,true,-158,-135 +true,true,-157,-135 +true,true,-156,-135 +true,true,-136,-135 +true,true,-135,-135 +true,true,-134,-135 +true,true,-114,-135 +true,true,-113,-135 +true,true,-112,-90 +true,true,-111,-90 +true,true,-110,-90 +true,true,-91,-90 +true,true,-90,-90 +true,true,-89,-90 +true,true,-69,-90 +true,true,-68,-90 +true,true,-67,-45 +true,true,-66,-45 +true,true,-65,-45 +true,true,-46,-45 +true,true,-45,-45 +true,true,-44,-45 +true,true,-24,-45 +true,true,-23,-45 +true,true,-22,0 +true,true,-21,0 +true,true,-20,0 +true,true,-1,0 +true,true,0,0 +true,true,1,0 +true,true,21,0 +true,true,22,0 +true,true,23,45 +true,true,24,45 +true,true,25,45 +true,true,44,45 +true,true,45,45 +true,true,46,45 +true,true,66,45 +true,true,67,45 +true,true,68,90 +true,true,69,90 +true,true,70,90 +true,true,89,90 +true,true,90,90 +true,true,91,90 +true,true,111,90 +true,true,112,90 +true,true,113,135 +true,true,114,135 +true,true,115,135 +true,true,134,135 +true,true,135,135 +true,true,136,135 +true,true,156,135 +true,true,157,135 +true,true,158,180 +true,true,159,180 +true,true,160,180 +true,true,179,180 +true,true,180,180 diff --git a/src/test/resources/net/rptools/maptool/model/SquareGrid.nextFacing.csv b/src/test/resources/net/rptools/maptool/model/SquareGrid.nextFacing.csv new file mode 100644 index 0000000000..87e3e15f51 --- /dev/null +++ b/src/test/resources/net/rptools/maptool/model/SquareGrid.nextFacing.csv @@ -0,0 +1,380 @@ +faceEdges,faceVertices,clockwise,facing,expected + +false,false,true,-179,90 +false,false,true,-178,90 +false,false,true,-158,90 +false,false,true,-157,90 +false,false,true,-156,90 +false,false,true,-136,90 +false,false,true,-135,90 +false,false,true,-134,90 +false,false,true,-114,90 +false,false,true,-113,90 +false,false,true,-112,90 +false,false,true,-111,90 +false,false,true,-110,90 +false,false,true,-91,90 +false,false,true,-90,90 +false,false,true,-89,90 +false,false,true,-69,90 +false,false,true,-68,90 +false,false,true,-67,90 +false,false,true,-66,90 +false,false,true,-65,90 +false,false,true,-46,90 +false,false,true,-45,90 +false,false,true,-44,90 +false,false,true,-24,90 +false,false,true,-23,90 +false,false,true,-22,90 +false,false,true,-21,90 +false,false,true,-20,90 +false,false,true,-1,90 +false,false,true,0,90 +false,false,true,1,90 +false,false,true,21,90 +false,false,true,22,90 +false,false,true,23,90 +false,false,true,24,90 +false,false,true,25,90 +false,false,true,44,90 +false,false,true,45,90 +false,false,true,46,90 +false,false,true,66,90 +false,false,true,67,90 +false,false,true,68,90 +false,false,true,69,90 +false,false,true,70,90 +false,false,true,89,90 +false,false,true,90,90 +false,false,true,91,90 +false,false,true,111,90 +false,false,true,112,90 +false,false,true,113,90 +false,false,true,114,90 +false,false,true,115,90 +false,false,true,134,90 +false,false,true,135,90 +false,false,true,136,90 +false,false,true,156,90 +false,false,true,157,90 +false,false,true,158,90 +false,false,true,159,90 +false,false,true,160,90 +false,false,true,179,90 +false,false,true,180,90 + +false,false,true,-179,90 +false,false,true,-178,90 +false,false,true,-158,90 +false,false,true,-157,90 +false,false,true,-156,90 +false,false,true,-136,90 +false,false,true,-135,90 +false,false,true,-134,90 +false,false,true,-114,90 +false,false,true,-113,90 +false,false,true,-112,90 +false,false,true,-111,90 +false,false,true,-110,90 +false,false,true,-91,90 +false,false,true,-90,90 +false,false,true,-89,90 +false,false,true,-69,90 +false,false,true,-68,90 +false,false,true,-67,90 +false,false,true,-66,90 +false,false,true,-65,90 +false,false,true,-46,90 +false,false,true,-45,90 +false,false,true,-44,90 +false,false,true,-24,90 +false,false,true,-23,90 +false,false,true,-22,90 +false,false,true,-21,90 +false,false,true,-20,90 +false,false,true,-1,90 +false,false,true,0,90 +false,false,true,1,90 +false,false,true,21,90 +false,false,true,22,90 +false,false,true,23,90 +false,false,true,24,90 +false,false,true,25,90 +false,false,true,44,90 +false,false,true,45,90 +false,false,true,46,90 +false,false,true,66,90 +false,false,true,67,90 +false,false,true,68,90 +false,false,true,69,90 +false,false,true,70,90 +false,false,true,89,90 +false,false,true,90,90 +false,false,true,91,90 +false,false,true,111,90 +false,false,true,112,90 +false,false,true,113,90 +false,false,true,114,90 +false,false,true,115,90 +false,false,true,134,90 +false,false,true,135,90 +false,false,true,136,90 +false,false,true,156,90 +false,false,true,157,90 +false,false,true,158,90 +false,false,true,159,90 +false,false,true,160,90 +false,false,true,179,90 +false,false,true,180,90 + +true,false,false,-179,0 +true,false,false,-178,0 +true,false,false,-136,0 +true,false,false,-135,0 +true,false,false,-134,0 +true,false,false,-133,0 +true,false,false,-132,0 +true,false,false,-46,0 +true,false,false,-45,0 +true,false,false,-44,90 +true,false,false,-43,90 +true,false,false,-42,90 +true,false,false,44,90 +true,false,false,45,90 +true,false,false,46,180 +true,false,false,47,180 +true,false,false,48,180 +true,false,false,134,180 +true,false,false,135,180 +true,false,false,136,-90 +true,false,false,137,-90 +true,false,false,138,-90 +true,false,false,179,-90 +true,false,false,180,-90 + +true,false,true,-179,180 +true,false,true,-178,180 +true,false,true,-136,180 +true,false,true,-135,180 +true,false,true,-134,180 +true,false,true,-133,180 +true,false,true,-132,180 +true,false,true,-91,180 +true,false,true,-90,180 +true,false,true,-89,180 +true,false,true,-46,180 +true,false,true,-45,180 +true,false,true,-44,-90 +true,false,true,-43,-90 +true,false,true,-42,-90 +true,false,true,-1,-90 +true,false,true,0,-90 +true,false,true,1,-90 +true,false,true,44,-90 +true,false,true,45,-90 +true,false,true,46,0 +true,false,true,47,0 +true,false,true,48,0 +true,false,true,89,0 +true,false,true,90,0 +true,false,true,91,0 +true,false,true,134,0 +true,false,true,135,0 +true,false,true,136,90 +true,false,true,137,90 +true,false,true,138,90 +true,false,true,179,90 +true,false,true,180,90 + +false,true,false,-179,-45 +false,true,false,-178,-45 +false,true,false,-136,-45 +false,true,false,-135,-45 +false,true,false,-134,-45 +false,true,false,-91,-45 +false,true,false,-90,-45 +false,true,false,-89,45 +false,true,false,-88,45 +false,true,false,-87,45 +false,true,false,-46,45 +false,true,false,-45,45 +false,true,false,-44,45 +false,true,false,-2,45 +false,true,false,-1,45 +false,true,false,0,45 +false,true,false,1,135 +false,true,false,2,135 +false,true,false,44,135 +false,true,false,45,135 +false,true,false,46,135 +false,true,false,89,135 +false,true,false,90,135 +false,true,false,91,-135 +false,true,false,92,-135 +false,true,false,93,-135 +false,true,false,134,-135 +false,true,false,135,-135 +false,true,false,136,-135 +false,true,false,179,-135 +false,true,false,180,-135 + +false,true,true,-179,135 +false,true,true,-178,135 +false,true,true,-136,135 +false,true,true,-135,135 +false,true,true,-134,135 +false,true,true,-91,135 +false,true,true,-90,135 +false,true,true,-89,-135 +false,true,true,-88,-135 +false,true,true,-87,-135 +false,true,true,-46,-135 +false,true,true,-45,-135 +false,true,true,-44,-135 +false,true,true,-2,-135 +false,true,true,-1,-135 +false,true,true,0,-135 +false,true,true,1,-45 +false,true,true,2,-45 +false,true,true,44,-45 +false,true,true,45,-45 +false,true,true,46,-45 +false,true,true,89,-45 +false,true,true,90,-45 +false,true,true,91,45 +false,true,true,92,45 +false,true,true,93,45 +false,true,true,134,45 +false,true,true,135,45 +false,true,true,136,45 +false,true,true,179,45 +false,true,true,180,45 + +true,true,false,-179,-90 +true,true,false,-178,-90 +true,true,false,-158,-90 +true,true,false,-157,-90 +true,true,false,-156,-90 +true,true,false,-136,-90 +true,true,false,-135,-90 +true,true,false,-134,-90 +true,true,false,-114,-90 +true,true,false,-113,-90 +true,true,false,-112,-45 +true,true,false,-111,-45 +true,true,false,-110,-45 +true,true,false,-91,-45 +true,true,false,-90,-45 +true,true,false,-89,-45 +true,true,false,-69,-45 +true,true,false,-68,-45 +true,true,false,-67,0 +true,true,false,-66,0 +true,true,false,-65,0 +true,true,false,-46,0 +true,true,false,-45,0 +true,true,false,-44,0 +true,true,false,-24,0 +true,true,false,-23,0 +true,true,false,-22,45 +true,true,false,-21,45 +true,true,false,-20,45 +true,true,false,-1,45 +true,true,false,0,45 +true,true,false,1,45 +true,true,false,21,45 +true,true,false,22,45 +true,true,false,23,90 +true,true,false,24,90 +true,true,false,25,90 +true,true,false,44,90 +true,true,false,45,90 +true,true,false,46,90 +true,true,false,66,90 +true,true,false,67,90 +true,true,false,68,135 +true,true,false,69,135 +true,true,false,70,135 +true,true,false,89,135 +true,true,false,90,135 +true,true,false,91,135 +true,true,false,111,135 +true,true,false,112,135 +true,true,false,113,180 +true,true,false,114,180 +true,true,false,115,180 +true,true,false,134,180 +true,true,false,135,180 +true,true,false,136,180 +true,true,false,156,180 +true,true,false,157,180 +true,true,false,158,-135 +true,true,false,159,-135 +true,true,false,160,-135 +true,true,false,179,-135 +true,true,false,180,-135 + +true,true,true,-179,180 +true,true,true,-178,180 +true,true,true,-158,180 +true,true,true,-157,180 +true,true,true,-156,180 +true,true,true,-136,180 +true,true,true,-135,180 +true,true,true,-134,180 +true,true,true,-114,180 +true,true,true,-113,180 +true,true,true,-112,-135 +true,true,true,-111,-135 +true,true,true,-110,-135 +true,true,true,-91,-135 +true,true,true,-90,-135 +true,true,true,-89,-135 +true,true,true,-69,-135 +true,true,true,-68,-135 +true,true,true,-67,-90 +true,true,true,-66,-90 +true,true,true,-65,-90 +true,true,true,-46,-90 +true,true,true,-45,-90 +true,true,true,-44,-90 +true,true,true,-24,-90 +true,true,true,-23,-90 +true,true,true,-22,-45 +true,true,true,-21,-45 +true,true,true,-20,-45 +true,true,true,-1,-45 +true,true,true,0,-45 +true,true,true,1,-45 +true,true,true,21,-45 +true,true,true,22,-45 +true,true,true,23,0 +true,true,true,24,0 +true,true,true,25,0 +true,true,true,44,0 +true,true,true,45,0 +true,true,true,46,0 +true,true,true,66,0 +true,true,true,67,0 +true,true,true,68,45 +true,true,true,69,45 +true,true,true,70,45 +true,true,true,89,45 +true,true,true,90,45 +true,true,true,91,45 +true,true,true,111,45 +true,true,true,112,45 +true,true,true,113,90 +true,true,true,114,90 +true,true,true,115,90 +true,true,true,134,90 +true,true,true,135,90 +true,true,true,136,90 +true,true,true,156,90 +true,true,true,157,90 +true,true,true,158,135 +true,true,true,159,135 +true,true,true,160,135 +true,true,true,179,135 +true,true,true,180,135 From 399c740feb3ff190b2af33e6872f955ccf6ad5ea Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Mon, 30 Sep 2024 14:07:37 -0700 Subject: [PATCH 032/146] Reimplement nearestFacing() and nextFacing() mathematically Instead of a provide a hardcoded array of possible facings, we now calculate the nearest facing using some modular arithmetic and rounding. This fixes the bugs mentioned in the prior commit, and test cases have been updated to match. Internally to `Grid`, the use of `getFacingAngles(boolean faceEdges, boolean faceVertices)` and been replaced with `snapFacingInternal(int facing, boolean faceEdges, boolean faceVertices, int addedSteps)`, which is enough to provide the functionality of both `getNearestFacing()` and `getNextFacing()`. --- .../java/net/rptools/maptool/model/Grid.java | 61 ++++++++++++------- .../rptools/maptool/model/GridlessGrid.java | 16 ++++- .../maptool/model/HexGridHorizontal.java | 29 ++++++--- .../maptool/model/HexGridVertical.java | 30 ++++++--- .../rptools/maptool/model/IsometricGrid.java | 27 +++++--- .../net/rptools/maptool/model/SquareGrid.java | 29 +++++---- .../net/rptools/maptool/util/TokenUtil.java | 13 ---- .../model/GridlessGrid.nearestFacing.csv | 24 ++++---- .../maptool/model/GridlessGrid.nextFacing.csv | 48 +++++++-------- .../model/HexGridHorizontal.nearestFacing.csv | 16 ++--- .../model/HexGridHorizontal.nextFacing.csv | 32 +++++----- .../model/HexGridVertical.nearestFacing.csv | 16 ++--- .../model/HexGridVertical.nextFacing.csv | 32 +++++----- .../model/IsometricGrid.nearestFacing.csv | 14 ++--- .../model/IsometricGrid.nextFacing.csv | 28 ++++----- .../model/SquareGrid.nearestFacing.csv | 14 ++--- .../maptool/model/SquareGrid.nextFacing.csv | 28 ++++----- 17 files changed, 253 insertions(+), 204 deletions(-) diff --git a/src/main/java/net/rptools/maptool/model/Grid.java b/src/main/java/net/rptools/maptool/model/Grid.java index 4f36e06c05..32606e7108 100644 --- a/src/main/java/net/rptools/maptool/model/Grid.java +++ b/src/main/java/net/rptools/maptool/model/Grid.java @@ -41,7 +41,6 @@ import net.rptools.maptool.model.zones.GridChanged; import net.rptools.maptool.server.proto.GridDto; import net.rptools.maptool.util.GraphicsUtil; -import net.rptools.maptool.util.TokenUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -121,37 +120,53 @@ public void drawCoordinatesOverlay(Graphics2D g, ZoneRenderer renderer) { * @param clockwise * @return */ - public int nextFacing(int facing, boolean faceEdges, boolean faceVertices, boolean clockwise) { - int[] facingArray = getFacingAngles(faceEdges, faceVertices); - int facingIndex = TokenUtil.getIndexNearestTo(facingArray, facing); + public final int nextFacing( + int facing, boolean faceEdges, boolean faceVertices, boolean clockwise) { + // Work in range (0, 360] as it is easier for implementations. + // Will convert back to (-180,180] at the end. + facing = Math.floorMod(facing - 1, 360) + 1; - facingIndex += clockwise ? -1 : 1; - if (facingIndex < 0) { - facingIndex = facingArray.length - 1; - } - if (facingIndex == facingArray.length) { - facingIndex = 0; - } - return facingArray[facingIndex]; + int nextFacing = snapFacingInternal(facing, faceEdges, faceVertices, clockwise ? -1 : 1); + + return normalizeFacing(nextFacing); } - public int nearestFacing(int facing, boolean faceEdges, boolean faceVertices) { - int[] facingArray = getFacingAngles(faceEdges, faceVertices); - // TODO Much chance of overflow here. - return facingArray[TokenUtil.getIndexNearestTo(facingArray, facing)]; + public final int nearestFacing(int facing, boolean faceEdges, boolean faceVertices) { + // Work in range (0, 360] as it is easier for implementations. + // Will convert back to (-180,180] at the end. + facing = Math.floorMod(facing - 1, 360) + 1; + + int nearestFacing = snapFacingInternal(facing, faceEdges, faceVertices, 0); + + return normalizeFacing(nearestFacing); } /** - * Get the facing options for tokens/objects on a grid. Each grid type can providing facings to - * the edges, the vertices, both, or neither. + * Snaps a facing to the nearest edges or vertex, then optionally jumps to an adjacent one. * - *

If both are false, tokens on that grid will not be able to rotate with the mouse and - * keyboard controls for setting facing. + * @param facing The original facing. Must be set in the range 0 < facing <= 360. + * @param faceEdges If {@code true}, allow snapping the facing to the nearest edge. + * @param faceVertices If {@code true}, allow snapping the facing to the nearest vertex. + * @param addedSteps The number of edges or vertices to advance after snapping (depends on values + * of {@code faceEdges} and {@code faceVertices}. + * @return The snapped facing. Can be any integer. + */ + protected abstract int snapFacingInternal( + int facing, boolean faceEdges, boolean faceVertices, int addedSteps); + + /** + * Return an equivalent facing in the range (-180, 180]. * - * @param faceEdges - Tokens can face edges. - * @param faceVertices - Tokens can face vertices. + * @param facing + * @return */ - protected abstract int[] getFacingAngles(boolean faceEdges, boolean faceVertices); + private int normalizeFacing(int facing) { + facing = Math.floorMod(facing, 360); + if (facing > 180) { + facing -= 360; + } + return facing; + } /** * Return the Point (double precision) for pixel center of Cell diff --git a/src/main/java/net/rptools/maptool/model/GridlessGrid.java b/src/main/java/net/rptools/maptool/model/GridlessGrid.java index a4b71399ed..7df41a0c98 100644 --- a/src/main/java/net/rptools/maptool/model/GridlessGrid.java +++ b/src/main/java/net/rptools/maptool/model/GridlessGrid.java @@ -73,8 +73,20 @@ public List getFootprints() { } @Override - public int[] getFacingAngles(boolean faceEdges, boolean faceVertices) { - return new int[] {-135, -90, -45, 0, 45, 90, 135, 180}; + protected int snapFacingInternal( + int facing, boolean faceEdges, boolean faceVertices, int addedSteps) { + // Work in range (0, 360], it's easier. Will convert back to (-180,180] at the end. + facing = Math.floorMod(facing - 1, 360) + 1; + + /* The number of degrees between each standard facing. */ + int step = 45; + /* The position of the first standard facing CCW from zero. */ + int base = 0; + /* A modification applied to facing to get the nearest answer, not a modulo/int div answer. */ + int diff = (step - 1) / 2; + + int stepsFromBase = Math.floorDiv(facing + diff - base, step) + addedSteps; + return stepsFromBase * step + base; } @Override diff --git a/src/main/java/net/rptools/maptool/model/HexGridHorizontal.java b/src/main/java/net/rptools/maptool/model/HexGridHorizontal.java index 5e139eddca..ac8518bd6d 100644 --- a/src/main/java/net/rptools/maptool/model/HexGridHorizontal.java +++ b/src/main/java/net/rptools/maptool/model/HexGridHorizontal.java @@ -67,18 +67,27 @@ protected synchronized Map getGridShapeCache() { return gridShapeCache; } - @Override - public int[] getFacingAngles(boolean faceEdges, boolean faceVertices) { + protected int snapFacingInternal( + int facing, boolean faceEdges, boolean faceVertices, int addedSteps) { // TODO Distorted hexes surely require distorted facing angles. - if (faceEdges && faceVertices) { - return new int[] {-150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150, 180}; - } else if (faceVertices) { - return new int[] {-150, -90, -30, 30, 90, 150}; - } else if (faceEdges) { - return new int[] {-120, -60, 0, 60, 120, 180}; - } else { - return new int[] {90}; + + if (!faceEdges && !faceVertices) { + // Facing not support. Return a default answer. + return 90; } + + // Work in range (0, 360], it's easier. Will convert back to (-180,180] at the end. + facing = Math.floorMod(facing - 1, 360) + 1; + + /* The number of degrees between each standard facing. */ + int step = (faceEdges && faceVertices) ? 30 : 60; + /* The position of the first standard facing CCW from zero. */ + int base = (!faceEdges && faceVertices) ? 30 : 0; + /* A modification applied to facing to get the nearest answer, not a modulo/int div answer. */ + int diff = (step - 1) / 2; + + int stepsFromBase = Math.floorDiv(facing + diff - base, step) + addedSteps; + return stepsFromBase * step + base; } @Override diff --git a/src/main/java/net/rptools/maptool/model/HexGridVertical.java b/src/main/java/net/rptools/maptool/model/HexGridVertical.java index 6946ff51a6..df36bc7d80 100644 --- a/src/main/java/net/rptools/maptool/model/HexGridVertical.java +++ b/src/main/java/net/rptools/maptool/model/HexGridVertical.java @@ -80,17 +80,27 @@ public double cellDistance(CellPoint cellA, CellPoint cellB, WalkerMetric wmetri } } - @Override - public int[] getFacingAngles(boolean faceEdges, boolean faceVertices) { - if (faceEdges && faceVertices) { - return new int[] {-150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150, 180}; - } else if (faceVertices) { - return new int[] {-120, -60, 0, 60, 120, 180}; - } else if (faceEdges) { - return new int[] {-150, -90, -30, 30, 90, 150}; - } else { - return new int[] {90}; + protected int snapFacingInternal( + int facing, boolean faceEdges, boolean faceVertices, int addedSteps) { + // TODO Distorted hexes surely require distorted facing angles. + + if (!faceEdges && !faceVertices) { + // Facing not support. Return a default answer. + return 90; } + + // Work in range (0, 360], it's easier. Will convert back to (-180,180] at the end. + facing = Math.floorMod(facing - 1, 360) + 1; + + /* The number of degrees between each standard facing. */ + int step = (faceEdges && faceVertices) ? 30 : 60; + /* The position of the first standard facing CCW from zero. */ + int base = (!faceEdges && faceVertices) ? 0 : 30; + /* A modification applied to facing to get the nearest answer, not a modulo/int div answer. */ + int diff = (step - 1) / 2; + + int stepsFromBase = Math.floorDiv(facing + diff - base, step) + addedSteps; + return stepsFromBase * step + base; } /* diff --git a/src/main/java/net/rptools/maptool/model/IsometricGrid.java b/src/main/java/net/rptools/maptool/model/IsometricGrid.java index c2eb9dc33c..bd1c7d8a9e 100644 --- a/src/main/java/net/rptools/maptool/model/IsometricGrid.java +++ b/src/main/java/net/rptools/maptool/model/IsometricGrid.java @@ -91,16 +91,25 @@ public Dimension getCellOffset() { } @Override - public int[] getFacingAngles(boolean faceEdges, boolean faceVertices) { - if (faceEdges && faceVertices) { - return new int[] {-135, -90, -45, 0, 45, 90, 135, 180}; - } else if (faceVertices) { - return new int[] {-90, 0, 90, 180}; - } else if (faceEdges) { - return new int[] {-135, -45, 45, 135}; - } else { - return new int[] {90}; + protected int snapFacingInternal( + int facing, boolean faceEdges, boolean faceVertices, int addedSteps) { + if (!faceEdges && !faceVertices) { + // Facing not support. Return a default answer. + return 90; } + + // Work in range (0, 360], it's easier. Will convert back to (-180,180] at the end. + facing = Math.floorMod(facing - 1, 360) + 1; + + /* The number of degrees between each standard facing. */ + int step = (faceEdges && faceVertices) ? 45 : 90; + /* The position of the first standard facing CCW from zero. */ + int base = (faceEdges && !faceVertices) ? 45 : 0; + /* A modification applied to facing to get the nearest answer, not a modulo/int div answer. */ + int diff = (step - 1) / 2; + + int stepsFromBase = Math.floorDiv(facing + diff - base, step) + addedSteps; + return stepsFromBase * step + base; } @Override diff --git a/src/main/java/net/rptools/maptool/model/SquareGrid.java b/src/main/java/net/rptools/maptool/model/SquareGrid.java index 2d00c1863d..5c6e25cd07 100644 --- a/src/main/java/net/rptools/maptool/model/SquareGrid.java +++ b/src/main/java/net/rptools/maptool/model/SquareGrid.java @@ -134,18 +134,25 @@ protected void fillDto(GridDto.Builder dto) { } @Override - public int[] getFacingAngles(boolean faceEdges, boolean faceVertices) { - if (faceEdges && faceVertices) { - return new int[] {-135, -90, -45, 0, 45, 90, 135, 180}; - } else if (faceVertices) { - // && !faceEdges - return new int[] {-135, -45, 45, 135}; - } else if (faceEdges) { - // && !faceVertices - return new int[] {-90, 0, 90, 180}; - } else { - return new int[] {90}; + protected int snapFacingInternal( + int facing, boolean faceEdges, boolean faceVertices, int addedSteps) { + if (!faceEdges && !faceVertices) { + // Facing not support. Return a default answer. + return 90; } + + // Work in range (0, 360], it's easier. Will convert back to (-180,180] at the end. + facing = Math.floorMod(facing - 1, 360) + 1; + + /* The number of degrees between each standard facing. */ + int step = (faceEdges && faceVertices) ? 45 : 90; + /* The position of the first standard facing CCW from zero. */ + int base = (!faceEdges && faceVertices) ? 45 : 0; + /* A modification applied to facing to get the nearest answer, not a modulo/int div answer. */ + int diff = (step - 1) / 2; + + int stepsFromBase = Math.floorDiv(facing + diff - base, step) + addedSteps; + return stepsFromBase * step + base; } @Override diff --git a/src/main/java/net/rptools/maptool/util/TokenUtil.java b/src/main/java/net/rptools/maptool/util/TokenUtil.java index 3bd68b1a8c..59294912fc 100644 --- a/src/main/java/net/rptools/maptool/util/TokenUtil.java +++ b/src/main/java/net/rptools/maptool/util/TokenUtil.java @@ -26,19 +26,6 @@ public class TokenUtil { private static final Logger log = LogManager.getLogger(TokenUtil.class); - public static int getIndexNearestTo(int[] array, int value) { - int delta = -1; - int closest = -1; - for (int i = 0; i < array.length; i++) { - int currDelta = Math.abs(value - array[i]); - if (delta < 0 || currDelta < delta) { - closest = i; - delta = currDelta; - } - } - return closest; - } - public static Token.TokenShape guessTokenType(Image image) { if (image instanceof BufferedImage) { return guessTokenType((BufferedImage) image); diff --git a/src/test/resources/net/rptools/maptool/model/GridlessGrid.nearestFacing.csv b/src/test/resources/net/rptools/maptool/model/GridlessGrid.nearestFacing.csv index ea71e1c8ed..5e8139d1e2 100644 --- a/src/test/resources/net/rptools/maptool/model/GridlessGrid.nearestFacing.csv +++ b/src/test/resources/net/rptools/maptool/model/GridlessGrid.nearestFacing.csv @@ -1,8 +1,8 @@ faceEdges,faceVertices,facing,expected -false,false,-179,-135 -false,false,-178,-135 -false,false,-158,-135 +false,false,-179,180 +false,false,-178,180 +false,false,-158,180 false,false,-157,-135 false,false,-156,-135 false,false,-136,-135 @@ -64,9 +64,9 @@ false,false,160,180 false,false,179,180 false,false,180,180 -true,false,-179,-135 -true,false,-178,-135 -true,false,-158,-135 +true,false,-179,180 +true,false,-178,180 +true,false,-158,180 true,false,-157,-135 true,false,-156,-135 true,false,-136,-135 @@ -128,9 +128,9 @@ true,false,160,180 true,false,179,180 true,false,180,180 -false,true,-179,-135 -false,true,-178,-135 -false,true,-158,-135 +false,true,-179,180 +false,true,-178,180 +false,true,-158,180 false,true,-157,-135 false,true,-156,-135 false,true,-136,-135 @@ -192,9 +192,9 @@ false,true,160,180 false,true,179,180 false,true,180,180 -true,true,-179,-135 -true,true,-178,-135 -true,true,-158,-135 +true,true,-179,180 +true,true,-178,180 +true,true,-158,180 true,true,-157,-135 true,true,-156,-135 true,true,-136,-135 diff --git a/src/test/resources/net/rptools/maptool/model/GridlessGrid.nextFacing.csv b/src/test/resources/net/rptools/maptool/model/GridlessGrid.nextFacing.csv index 0ae615e38b..e7b7c33f3f 100644 --- a/src/test/resources/net/rptools/maptool/model/GridlessGrid.nextFacing.csv +++ b/src/test/resources/net/rptools/maptool/model/GridlessGrid.nextFacing.csv @@ -1,8 +1,8 @@ faceEdges,faceVertices,clockwise,facing,expected -false,false,false,-179,-90 -false,false,false,-178,-90 -false,false,false,-158,-90 +false,false,false,-179,-135 +false,false,false,-178,-135 +false,false,false,-158,-135 false,false,false,-157,-90 false,false,false,-156,-90 false,false,false,-136,-90 @@ -64,9 +64,9 @@ false,false,false,160,-135 false,false,false,179,-135 false,false,false,180,-135 -false,false,true,-179,180 -false,false,true,-178,180 -false,false,true,-158,180 +false,false,true,-179,135 +false,false,true,-178,135 +false,false,true,-158,135 false,false,true,-157,180 false,false,true,-156,180 false,false,true,-136,180 @@ -128,9 +128,9 @@ false,false,true,160,135 false,false,true,179,135 false,false,true,180,135 -true,false,false,-179,-90 -true,false,false,-178,-90 -true,false,false,-158,-90 +true,false,false,-179,-135 +true,false,false,-178,-135 +true,false,false,-158,-135 true,false,false,-157,-90 true,false,false,-156,-90 true,false,false,-136,-90 @@ -192,9 +192,9 @@ true,false,false,160,-135 true,false,false,179,-135 true,false,false,180,-135 -true,false,true,-179,180 -true,false,true,-178,180 -true,false,true,-158,180 +true,false,true,-179,135 +true,false,true,-178,135 +true,false,true,-158,135 true,false,true,-157,180 true,false,true,-156,180 true,false,true,-136,180 @@ -256,9 +256,9 @@ true,false,true,160,135 true,false,true,179,135 true,false,true,180,135 -false,true,false,-179,-90 -false,true,false,-178,-90 -false,true,false,-158,-90 +false,true,false,-179,-135 +false,true,false,-178,-135 +false,true,false,-158,-135 false,true,false,-157,-90 false,true,false,-156,-90 false,true,false,-136,-90 @@ -320,9 +320,9 @@ false,true,false,160,-135 false,true,false,179,-135 false,true,false,180,-135 -false,true,true,-179,180 -false,true,true,-178,180 -false,true,true,-158,180 +false,true,true,-179,135 +false,true,true,-178,135 +false,true,true,-158,135 false,true,true,-157,180 false,true,true,-156,180 false,true,true,-136,180 @@ -384,9 +384,9 @@ false,true,true,160,135 false,true,true,179,135 false,true,true,180,135 -true,true,false,-179,-90 -true,true,false,-178,-90 -true,true,false,-158,-90 +true,true,false,-179,-135 +true,true,false,-178,-135 +true,true,false,-158,-135 true,true,false,-157,-90 true,true,false,-156,-90 true,true,false,-136,-90 @@ -448,9 +448,9 @@ true,true,false,160,-135 true,true,false,179,-135 true,true,false,180,-135 -true,true,true,-179,180 -true,true,true,-178,180 -true,true,true,-158,180 +true,true,true,-179,135 +true,true,true,-178,135 +true,true,true,-158,135 true,true,true,-157,180 true,true,true,-156,180 true,true,true,-136,180 diff --git a/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nearestFacing.csv b/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nearestFacing.csv index 090831395e..298a4209fb 100644 --- a/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nearestFacing.csv +++ b/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nearestFacing.csv @@ -14,10 +14,10 @@ false,false,120,90 false,false,150,90 false,false,180,90 -true,false,-179,-120 -true,false,-178,-120 -true,false,-151,-120 -true,false,-150,-120 +true,false,-179,180 +true,false,-178,180 +true,false,-151,180 +true,false,-150,180 true,false,-149,-120 true,false,-121,-120 true,false,-120,-120 @@ -90,10 +90,10 @@ false,true,151,150 false,true,179,150 false,true,180,150 -true,true,-179,-150 -true,true,-178,-150 -true,true,-166,-150 -true,true,-165,-150 +true,true,-179,180 +true,true,-178,180 +true,true,-166,180 +true,true,-165,180 true,true,-164,-150 true,true,-151,-150 true,true,-150,-150 diff --git a/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nextFacing.csv b/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nextFacing.csv index 5ee0b8c314..a1c2a5bf25 100644 --- a/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nextFacing.csv +++ b/src/test/resources/net/rptools/maptool/model/HexGridHorizontal.nextFacing.csv @@ -28,10 +28,10 @@ false,false,true,120,90 false,false,true,150,90 false,false,true,180,90 -true,false,false,-179,-60 -true,false,false,-178,-60 -true,false,false,-151,-60 -true,false,false,-150,-60 +true,false,false,-179,-120 +true,false,false,-178,-120 +true,false,false,-151,-120 +true,false,false,-150,-120 true,false,false,-149,-60 true,false,false,-121,-60 true,false,false,-120,-60 @@ -66,10 +66,10 @@ true,false,false,151,-120 true,false,false,179,-120 true,false,false,180,-120 -true,false,true,-179,180 -true,false,true,-178,180 -true,false,true,-151,180 -true,false,true,-150,180 +true,false,true,-179,120 +true,false,true,-178,120 +true,false,true,-151,120 +true,false,true,-150,120 true,false,true,-149,180 true,false,true,-121,180 true,false,true,-120,180 @@ -180,10 +180,10 @@ false,true,true,151,90 false,true,true,179,90 false,true,true,180,90 -true,true,false,-179,-120 -true,true,false,-178,-120 -true,true,false,-166,-120 -true,true,false,-165,-120 +true,true,false,-179,-150 +true,true,false,-178,-150 +true,true,false,-166,-150 +true,true,false,-165,-150 true,true,false,-164,-120 true,true,false,-151,-120 true,true,false,-150,-120 @@ -254,10 +254,10 @@ true,true,false,166,-150 true,true,false,179,-150 true,true,false,180,-150 -true,true,true,-179,180 -true,true,true,-178,180 -true,true,true,-166,180 -true,true,true,-165,180 +true,true,true,-179,150 +true,true,true,-178,150 +true,true,true,-166,150 +true,true,true,-165,150 true,true,true,-164,180 true,true,true,-151,180 true,true,true,-150,180 diff --git a/src/test/resources/net/rptools/maptool/model/HexGridVertical.nearestFacing.csv b/src/test/resources/net/rptools/maptool/model/HexGridVertical.nearestFacing.csv index 52a5cbc3e5..084b416fd8 100644 --- a/src/test/resources/net/rptools/maptool/model/HexGridVertical.nearestFacing.csv +++ b/src/test/resources/net/rptools/maptool/model/HexGridVertical.nearestFacing.csv @@ -52,10 +52,10 @@ true,false,151,150 true,false,179,150 true,false,180,150 -false,true,-179,-120 -false,true,-178,-120 -false,true,-151,-120 -false,true,-150,-120 +false,true,-179,180 +false,true,-178,180 +false,true,-151,180 +false,true,-150,180 false,true,-149,-120 false,true,-121,-120 false,true,-120,-120 @@ -90,10 +90,10 @@ false,true,151,180 false,true,179,180 false,true,180,180 -true,true,-179,-150 -true,true,-178,-150 -true,true,-166,-150 -true,true,-165,-150 +true,true,-179,180 +true,true,-178,180 +true,true,-166,180 +true,true,-165,180 true,true,-164,-150 true,true,-151,-150 true,true,-150,-150 diff --git a/src/test/resources/net/rptools/maptool/model/HexGridVertical.nextFacing.csv b/src/test/resources/net/rptools/maptool/model/HexGridVertical.nextFacing.csv index 342ba1306b..82b3af9cbb 100644 --- a/src/test/resources/net/rptools/maptool/model/HexGridVertical.nextFacing.csv +++ b/src/test/resources/net/rptools/maptool/model/HexGridVertical.nextFacing.csv @@ -28,10 +28,10 @@ false,false,true,120,90 false,false,true,150,90 false,false,true,180,90 -false,true,false,-179,-60 -false,true,false,-178,-60 -false,true,false,-151,-60 -false,true,false,-150,-60 +false,true,false,-179,-120 +false,true,false,-178,-120 +false,true,false,-151,-120 +false,true,false,-150,-120 false,true,false,-149,-60 false,true,false,-121,-60 false,true,false,-120,-60 @@ -66,10 +66,10 @@ false,true,false,151,-120 false,true,false,179,-120 false,true,false,180,-120 -false,true,true,-179,180 -false,true,true,-178,180 -false,true,true,-151,180 -false,true,true,-150,180 +false,true,true,-179,120 +false,true,true,-178,120 +false,true,true,-151,120 +false,true,true,-150,120 false,true,true,-149,180 false,true,true,-121,180 false,true,true,-120,180 @@ -180,10 +180,10 @@ true,false,true,151,90 true,false,true,179,90 true,false,true,180,90 -true,true,false,-179,-120 -true,true,false,-178,-120 -true,true,false,-166,-120 -true,true,false,-165,-120 +true,true,false,-179,-150 +true,true,false,-178,-150 +true,true,false,-166,-150 +true,true,false,-165,-150 true,true,false,-164,-120 true,true,false,-151,-120 true,true,false,-150,-120 @@ -254,10 +254,10 @@ true,true,false,166,-150 true,true,false,179,-150 true,true,false,180,-150 -true,true,true,-179,180 -true,true,true,-178,180 -true,true,true,-166,180 -true,true,true,-165,180 +true,true,true,-179,150 +true,true,true,-178,150 +true,true,true,-166,150 +true,true,true,-165,150 true,true,true,-164,180 true,true,true,-151,180 true,true,true,-150,180 diff --git a/src/test/resources/net/rptools/maptool/model/IsometricGrid.nearestFacing.csv b/src/test/resources/net/rptools/maptool/model/IsometricGrid.nearestFacing.csv index 70e65f7eb0..cd56fc5e9a 100644 --- a/src/test/resources/net/rptools/maptool/model/IsometricGrid.nearestFacing.csv +++ b/src/test/resources/net/rptools/maptool/model/IsometricGrid.nearestFacing.csv @@ -96,10 +96,10 @@ true,false,136,135 true,false,179,135 true,false,180,135 -false,true,-179,-90 -false,true,-178,-90 -false,true,-136,-90 -false,true,-135,-90 +false,true,-179,180 +false,true,-178,180 +false,true,-136,180 +false,true,-135,180 false,true,-134,-90 false,true,-133,-90 false,true,-132,-90 @@ -130,9 +130,9 @@ false,true,138,180 false,true,179,180 false,true,180,180 -true,true,-179,-135 -true,true,-178,-135 -true,true,-158,-135 +true,true,-179,180 +true,true,-178,180 +true,true,-158,180 true,true,-157,-135 true,true,-156,-135 true,true,-136,-135 diff --git a/src/test/resources/net/rptools/maptool/model/IsometricGrid.nextFacing.csv b/src/test/resources/net/rptools/maptool/model/IsometricGrid.nextFacing.csv index 2775fd4620..2cd65c1696 100644 --- a/src/test/resources/net/rptools/maptool/model/IsometricGrid.nextFacing.csv +++ b/src/test/resources/net/rptools/maptool/model/IsometricGrid.nextFacing.csv @@ -192,10 +192,10 @@ true,false,true,136,45 true,false,true,179,45 true,false,true,180,45 -false,true,false,-179,0 -false,true,false,-178,0 -false,true,false,-136,0 -false,true,false,-135,0 +false,true,false,-179,-90 +false,true,false,-178,-90 +false,true,false,-136,-90 +false,true,false,-135,-90 false,true,false,-134,0 false,true,false,-133,0 false,true,false,-132,0 @@ -217,10 +217,10 @@ false,true,false,138,-90 false,true,false,179,-90 false,true,false,180,-90 -false,true,true,-179,180 -false,true,true,-178,180 -false,true,true,-136,180 -false,true,true,-135,180 +false,true,true,-179,90 +false,true,true,-178,90 +false,true,true,-136,90 +false,true,true,-135,90 false,true,true,-134,180 false,true,true,-133,180 false,true,true,-132,180 @@ -251,9 +251,9 @@ false,true,true,138,90 false,true,true,179,90 false,true,true,180,90 -true,true,false,-179,-90 -true,true,false,-178,-90 -true,true,false,-158,-90 +true,true,false,-179,-135 +true,true,false,-178,-135 +true,true,false,-158,-135 true,true,false,-157,-90 true,true,false,-156,-90 true,true,false,-136,-90 @@ -315,9 +315,9 @@ true,true,false,160,-135 true,true,false,179,-135 true,true,false,180,-135 -true,true,true,-179,180 -true,true,true,-178,180 -true,true,true,-158,180 +true,true,true,-179,135 +true,true,true,-178,135 +true,true,true,-158,135 true,true,true,-157,180 true,true,true,-156,180 true,true,true,-136,180 diff --git a/src/test/resources/net/rptools/maptool/model/SquareGrid.nearestFacing.csv b/src/test/resources/net/rptools/maptool/model/SquareGrid.nearestFacing.csv index 37acc1d034..5e95c1105b 100644 --- a/src/test/resources/net/rptools/maptool/model/SquareGrid.nearestFacing.csv +++ b/src/test/resources/net/rptools/maptool/model/SquareGrid.nearestFacing.csv @@ -64,10 +64,10 @@ false,false,160,90 false,false,179,90 false,false,180,90 -true,false,-179,-90 -true,false,-178,-90 -true,false,-136,-90 -true,false,-135,-90 +true,false,-179,180 +true,false,-178,180 +true,false,-136,180 +true,false,-135,180 true,false,-134,-90 true,false,-133,-90 true,false,-132,-90 @@ -130,9 +130,9 @@ false,true,136,135 false,true,179,135 false,true,180,135 -true,true,-179,-135 -true,true,-178,-135 -true,true,-158,-135 +true,true,-179,180 +true,true,-178,180 +true,true,-158,180 true,true,-157,-135 true,true,-156,-135 true,true,-136,-135 diff --git a/src/test/resources/net/rptools/maptool/model/SquareGrid.nextFacing.csv b/src/test/resources/net/rptools/maptool/model/SquareGrid.nextFacing.csv index 87e3e15f51..4d150153cb 100644 --- a/src/test/resources/net/rptools/maptool/model/SquareGrid.nextFacing.csv +++ b/src/test/resources/net/rptools/maptool/model/SquareGrid.nextFacing.csv @@ -128,10 +128,10 @@ false,false,true,160,90 false,false,true,179,90 false,false,true,180,90 -true,false,false,-179,0 -true,false,false,-178,0 -true,false,false,-136,0 -true,false,false,-135,0 +true,false,false,-179,-90 +true,false,false,-178,-90 +true,false,false,-136,-90 +true,false,false,-135,-90 true,false,false,-134,0 true,false,false,-133,0 true,false,false,-132,0 @@ -153,10 +153,10 @@ true,false,false,138,-90 true,false,false,179,-90 true,false,false,180,-90 -true,false,true,-179,180 -true,false,true,-178,180 -true,false,true,-136,180 -true,false,true,-135,180 +true,false,true,-179,90 +true,false,true,-178,90 +true,false,true,-136,90 +true,false,true,-135,90 true,false,true,-134,180 true,false,true,-133,180 true,false,true,-132,180 @@ -251,9 +251,9 @@ false,true,true,136,45 false,true,true,179,45 false,true,true,180,45 -true,true,false,-179,-90 -true,true,false,-178,-90 -true,true,false,-158,-90 +true,true,false,-179,-135 +true,true,false,-178,-135 +true,true,false,-158,-135 true,true,false,-157,-90 true,true,false,-156,-90 true,true,false,-136,-90 @@ -315,9 +315,9 @@ true,true,false,160,-135 true,true,false,179,-135 true,true,false,180,-135 -true,true,true,-179,180 -true,true,true,-178,180 -true,true,true,-158,180 +true,true,true,-179,135 +true,true,true,-178,135 +true,true,true,-158,135 true,true,true,-157,180 true,true,true,-156,180 true,true,true,-136,180 From b6fe46f2ea0e8cf808e12941a17f40cd7417cd1b Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Fri, 27 Sep 2024 01:56:36 -0700 Subject: [PATCH 033/146] Grid.getShapedArea() and overrides no longer modifies token facing The implementation also uses `Token.getFacingInDegrees()` for easier case handling, though it requires shifting the result by 90. --- src/main/java/net/rptools/maptool/model/Grid.java | 5 +++-- .../java/net/rptools/maptool/model/IsometricGrid.java | 9 +++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/rptools/maptool/model/Grid.java b/src/main/java/net/rptools/maptool/model/Grid.java index 145b861ca2..f3458ebd4e 100644 --- a/src/main/java/net/rptools/maptool/model/Grid.java +++ b/src/main/java/net/rptools/maptool/model/Grid.java @@ -411,6 +411,7 @@ public void setSize(int size) { } int visionDistance = zone.getTokenVisionInPixels(); double visionRange = (range == 0) ? visionDistance : range * getSize() / zone.getUnitsPerCell(); + int tokenFacing = token.getFacingInDegrees() + 90; if (scaleWithToken) { double footprintWidth = token.getFootprint(this).getBounds(this).getWidth() / 2; @@ -453,7 +454,7 @@ public void setSize(int size) { visibleArea = new Area( AffineTransform.getRotateInstance( - Math.toRadians(offsetAngle) - Math.toRadians(token.getFacing())) + Math.toRadians(offsetAngle) + Math.toRadians(tokenFacing)) .createTransformedShape(visibleShape)); break; case CONE: @@ -475,7 +476,7 @@ public void setSize(int size) { // Rotate tempvisibleArea = tempvisibleArea.createTransformedArea( - AffineTransform.getRotateInstance(-Math.toRadians(token.getFacing()))); + AffineTransform.getRotateInstance(Math.toRadians(tokenFacing))); Rectangle footprint = token.getFootprint(this).getBounds(this); footprint.x = -footprint.width / 2; diff --git a/src/main/java/net/rptools/maptool/model/IsometricGrid.java b/src/main/java/net/rptools/maptool/model/IsometricGrid.java index 2e9d174227..50d65a52a5 100644 --- a/src/main/java/net/rptools/maptool/model/IsometricGrid.java +++ b/src/main/java/net/rptools/maptool/model/IsometricGrid.java @@ -281,6 +281,7 @@ public Area getShapedArea( int visionDistance = getZone().getTokenVisionInPixels(); double visionRange = (range == 0) ? visionDistance : range * getSize() / getZone().getUnitsPerCell(); + int tokenFacing = token.getFacingInDegrees() + 90; if (scaleWithToken) { Rectangle footprint = token.getFootprint(this).getBounds(this); @@ -311,7 +312,7 @@ public Area getShapedArea( Shape visibleShape = new Rectangle2D.Double(0, -pixelWidth / 2, visionRange, pixelWidth); // new angle, corrected for isometric view - double theta = Math.toRadians(offsetAngle) + Math.toRadians(token.getFacing()); + double theta = Math.toRadians(offsetAngle) - Math.toRadians(tokenFacing); Point2D angleVector = new Point2D.Double(Math.cos(theta), Math.sin(theta)); AffineTransform at = new AffineTransform(); at.rotate(Math.PI / 4); @@ -331,17 +332,13 @@ public Area getShapedArea( visionRange = (float) Math.sin(Math.toRadians(45)) * visionRange; // Get the cone, use degreesFromIso to convert the facing from isometric to plan - // Area tempvisibleArea = new Area(new Arc2D.Double(-visionRange * 2, -visionRange, - // visionRange * 4, visionRange * 2, token.getFacing() - (arcAngle / 2.0) + (offsetAngle * - // 1.0), arcAngle, - // Arc2D.PIE)); Arc2D cone = new Arc2D.Double( -visionRange * 2, -visionRange, visionRange * 4, visionRange * 2, - token.getFacing() - (arcAngle / 2.0) + (offsetAngle * 1.0), + -tokenFacing - (arcAngle / 2.0) + (offsetAngle * 1.0), arcAngle, Arc2D.PIE); GeneralPath path = new GeneralPath(); From 3eb4a9940fccd066c09abc4e5df98b4b7556d704 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Fri, 27 Sep 2024 10:48:14 -0700 Subject: [PATCH 034/146] Clean up Grid.getShapedArea() and implementations. This is just a matter of avoiding unnecessary work (repeated footprint lookups, unnecessary area transformations) and a bit of code style. No behavioural change. Error logs were added to the `default` case so that unknown shape types are not silenty handled. --- .../java/net/rptools/maptool/model/Grid.java | 71 +++++++++---------- .../rptools/maptool/model/IsometricGrid.java | 53 ++++++-------- .../java/net/rptools/maptool/model/Token.java | 4 +- 3 files changed, 56 insertions(+), 72 deletions(-) diff --git a/src/main/java/net/rptools/maptool/model/Grid.java b/src/main/java/net/rptools/maptool/model/Grid.java index f3458ebd4e..d319130f53 100644 --- a/src/main/java/net/rptools/maptool/model/Grid.java +++ b/src/main/java/net/rptools/maptool/model/Grid.java @@ -411,10 +411,12 @@ public void setSize(int size) { } int visionDistance = zone.getTokenVisionInPixels(); double visionRange = (range == 0) ? visionDistance : range * getSize() / zone.getUnitsPerCell(); - int tokenFacing = token.getFacingInDegrees() + 90; + /* Token facing as an angle. 0° points to the right and clockwise is positive. */ + int tokenFacingAngle = token.getFacingInDegrees() + 90; + Rectangle footprint = token.getFootprint(this).getBounds(this); if (scaleWithToken) { - double footprintWidth = token.getFootprint(this).getBounds(this).getWidth() / 2; + double footprintWidth = footprint.getWidth() / 2; // Test for gridless maps var cellShape = getCellShape(); @@ -424,85 +426,76 @@ public void setSize(int size) { } else { // For grids, this will be the same, but for Hex's we'll use the smaller side depending on // which Hex type you choose - double footprintHeight = token.getFootprint(this).getBounds(this).getHeight() / 2; + double footprintHeight = footprint.getHeight() / 2; visionRange += Math.min(footprintWidth, footprintHeight); } } - Area visibleArea = new Area(); + Area visibleArea; switch (shape) { - case CIRCLE: + case CIRCLE -> { visibleArea = GraphicsUtil.createLineSegmentEllipse( -visionRange, -visionRange, visionRange, visionRange, CIRCLE_SEGMENTS); - break; - case GRID: + } + case GRID -> { visibleArea = getGridArea(token, range, scaleWithToken, visionRange); - break; - case SQUARE: + } + case SQUARE -> { visibleArea = new Area( new Rectangle2D.Double( -visionRange, -visionRange, visionRange * 2, visionRange * 2)); - break; - case BEAM: + } + case BEAM -> { // Make at least 1 pixel on each side, so it's at least visible at 100% zoom. var pixelWidth = Math.max(2, width * getSize() / zone.getUnitsPerCell()); Shape lineShape = new Rectangle2D.Double(0, -pixelWidth / 2, visionRange, pixelWidth); - Shape visibleShape = new GeneralPath(lineShape); visibleArea = new Area( - AffineTransform.getRotateInstance( - Math.toRadians(offsetAngle) + Math.toRadians(tokenFacing)) - .createTransformedShape(visibleShape)); - break; - case CONE: + AffineTransform.getRotateInstance(Math.toRadians(offsetAngle + tokenFacingAngle)) + .createTransformedShape(lineShape)); + } + case CONE -> { Arc2D cone = new Arc2D.Double( -visionRange, -visionRange, visionRange * 2, visionRange * 2, - 360.0 - (arcAngle / 2.0) + (offsetAngle * 1.0), + (offsetAngle - tokenFacingAngle) - arcAngle / 2., arcAngle, Arc2D.PIE); // Flatten the cone to remove 'curves' GeneralPath path = new GeneralPath(); path.append(cone.getPathIterator(null, 1), false); - Area tempvisibleArea = new Area(path); - - // Rotate - tempvisibleArea = - tempvisibleArea.createTransformedArea( - AffineTransform.getRotateInstance(Math.toRadians(tokenFacing))); - - Rectangle footprint = token.getFootprint(this).getBounds(this); - footprint.x = -footprint.width / 2; - footprint.y = -footprint.height / 2; - - visibleArea.add(new Area(footprint)); - visibleArea.add(tempvisibleArea); - break; - case HEX: - footprint = token.getFootprint(this).getBounds(this); + visibleArea = new Area(path); + + var footprintPart = new Rectangle(footprint); + footprintPart.x = -footprintPart.width / 2; + footprintPart.y = -footprintPart.height / 2; + visibleArea.add(new Area(footprintPart)); + } + case HEX -> { double x = footprint.getCenterX(); double y = footprint.getCenterY(); - double footprintWidth = token.getFootprint(this).getBounds(this).getWidth(); - double footprintHeight = token.getFootprint(this).getBounds(this).getHeight(); + double footprintWidth = footprint.getWidth(); + double footprintHeight = footprint.getHeight(); double adjustment = Math.min(footprintWidth, footprintHeight); x -= adjustment / 2; y -= adjustment / 2; visibleArea = createHex(x, y, visionRange, 0); - break; - default: + } + default -> { + log.error("Unhandled shape {}; treating as a circle", shape); visibleArea = GraphicsUtil.createLineSegmentEllipse( -visionRange, -visionRange, visionRange * 2, visionRange * 2, CIRCLE_SEGMENTS); - break; + } } return visibleArea; diff --git a/src/main/java/net/rptools/maptool/model/IsometricGrid.java b/src/main/java/net/rptools/maptool/model/IsometricGrid.java index 50d65a52a5..319e6bb571 100644 --- a/src/main/java/net/rptools/maptool/model/IsometricGrid.java +++ b/src/main/java/net/rptools/maptool/model/IsometricGrid.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nonnull; import javax.swing.Action; import javax.swing.KeyStroke; import net.rptools.maptool.client.AppPreferences; @@ -267,7 +268,7 @@ public void uninstallMovementKeys(Map actionMap) { } @Override - public Area getShapedArea( + public @Nonnull Area getShapedArea( ShapeType shape, Token token, double range, @@ -281,38 +282,32 @@ public Area getShapedArea( int visionDistance = getZone().getTokenVisionInPixels(); double visionRange = (range == 0) ? visionDistance : range * getSize() / getZone().getUnitsPerCell(); - int tokenFacing = token.getFacingInDegrees() + 90; + int tokenFacingAngle = token.getFacingInDegrees() + 90; + Rectangle footprint = token.getFootprint(this).getBounds(this); if (scaleWithToken) { - Rectangle footprint = token.getFootprint(this).getBounds(this); visionRange += footprint.getHeight() / 2; - System.out.println(token.getName() + " footprint.getWidth() " + footprint.getWidth()); - System.out.println(token.getName() + " footprint.getHeight() " + footprint.getHeight()); } - // System.out.println("this.getDefaultFootprint() " + this.getDefaultFootprint()); - // System.out.println("token.getWidth() " + token.getWidth()); Area visibleArea = new Area(); switch (shape) { - case CIRCLE: + case CIRCLE -> { visionRange = (float) Math.sin(Math.toRadians(45)) * visionRange; - // visibleArea = new Area(new Ellipse2D.Double(-visionRange * 2, -visionRange, visionRange * - // 4, visionRange * 2)); visibleArea = GraphicsUtil.createLineSegmentEllipse( -visionRange * 2, -visionRange, visionRange * 2, visionRange, CIRCLE_SEGMENTS); - break; - case SQUARE: - int x[] = {0, (int) visionRange * 2, 0, (int) -visionRange * 2}; - int y[] = {(int) -visionRange, 0, (int) visionRange, 0}; + } + case SQUARE -> { + int[] x = {0, (int) visionRange * 2, 0, (int) -visionRange * 2}; + int[] y = {(int) -visionRange, 0, (int) visionRange, 0}; visibleArea = new Area(new Polygon(x, y, 4)); - break; - case BEAM: + } + case BEAM -> { var pixelWidth = Math.max(2, width * getSize() / getZone().getUnitsPerCell()); Shape visibleShape = new Rectangle2D.Double(0, -pixelWidth / 2, visionRange, pixelWidth); // new angle, corrected for isometric view - double theta = Math.toRadians(offsetAngle) - Math.toRadians(tokenFacing); + double theta = Math.toRadians(offsetAngle - tokenFacingAngle); Point2D angleVector = new Point2D.Double(Math.cos(theta), Math.sin(theta)); AffineTransform at = new AffineTransform(); at.rotate(Math.PI / 4); @@ -325,9 +320,8 @@ public Area getShapedArea( new Area( AffineTransform.getRotateInstance(theta + Math.toRadians(45)) .createTransformedShape(visibleShape)); - - break; - case CONE: + } + case CONE -> { // Rotate the vision range by 45 degrees for isometric view visionRange = (float) Math.sin(Math.toRadians(45)) * visionRange; // Get the cone, use degreesFromIso to convert the facing from isometric to plan @@ -338,18 +332,13 @@ public Area getShapedArea( -visionRange, visionRange * 4, visionRange * 2, - -tokenFacing - (arcAngle / 2.0) + (offsetAngle * 1.0), + (offsetAngle - tokenFacingAngle) - (arcAngle / 2.0), arcAngle, Arc2D.PIE); GeneralPath path = new GeneralPath(); path.append(cone.getPathIterator(null, 1), false); // Flatten the cone to remove 'curves' Area tempvisibleArea = new Area(path); - // Get the cell footprint - Rectangle footprint = - token.getFootprint(getZone().getGrid()).getBounds(getZone().getGrid()); - footprint.x = -footprint.width / 2; - footprint.y = -footprint.height / 2; // convert the cell footprint to an area Area cellShape = createCellShape(footprint.height); // convert the area to isometric view @@ -359,14 +348,14 @@ public Area getShapedArea( // join cell footprint and cone to create viewable area visibleArea.add(cellShape); visibleArea.add(tempvisibleArea); - break; - default: - // visibleArea = new Area(new Ellipse2D.Double(-visionRange, -visionRange, visionRange * 2, - // visionRange * 2)); + } + default -> { + log.error("Unhandled shape {}; treating as a circle", shape); + visionRange = (float) Math.sin(Math.toRadians(45)) * visionRange; visibleArea = GraphicsUtil.createLineSegmentEllipse( - -visionRange, -visionRange, visionRange, visionRange, CIRCLE_SEGMENTS); - break; + -visionRange * 2, -visionRange, visionRange * 2, visionRange, CIRCLE_SEGMENTS); + } } return visibleArea; } diff --git a/src/main/java/net/rptools/maptool/model/Token.java b/src/main/java/net/rptools/maptool/model/Token.java index 0252943ddd..7b9b94cab5 100644 --- a/src/main/java/net/rptools/maptool/model/Token.java +++ b/src/main/java/net/rptools/maptool/model/Token.java @@ -875,7 +875,9 @@ public int getFacing() { /** * This returns the rotation of the facing of the token from the default facing of down or -90. - * Positive for CW and negative for CCW. + * + *

Positive for CW and negative for CCW. The range is currently from -270° (inclusive) to +90° + * (exclusive), but callers should not rely on this. * * @return angle in degrees */ From d169c0d94fb83609b09027a28443563dadc90fad Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:00:03 +0800 Subject: [PATCH 035/146] Changed render area for states and bars. Now renders to a square area based on tokens max dimension for non isometric tokens. Area size is now based on non-rotated token bounds. --- .../client/ui/zone/renderer/ZoneRenderer.java | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java index 64ca8bba35..3d47998938 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java @@ -2598,22 +2598,40 @@ protected void renderTokens( timer.stop("tokenlist-8"); timer.start("tokenlist-9"); - // Set up the graphics so that the overlay can just be painted. + /** + * Paint States and Bars Set up the graphics so that the overlay can just be painted. Use + * non-rotated token bounds to avoid odd size changes + */ + Token tmpToken = new Token(token); + tmpToken.setFacing(null); + Rectangle tmpBounds = tmpToken.getBounds(this.zone); + + // Unless it is isometric, make it square to avoid distortion + double maxDim = Math.max(tmpBounds.getWidth(), tmpBounds.getHeight()); + // scale it to the view + maxDim *= this.getScale(); + Rectangle bounds; + if (zone.getGrid().isIsometric()) { + bounds = + new Rectangle( + 0, + 0, + (int) (tmpBounds.getWidth() * this.getScale()), + (int) (tmpBounds.getHeight() * this.getScale())); + } else { + bounds = new Rectangle(0, 0, (int) maxDim, (int) maxDim); + } + + // calculate the drawing region from the token centre. Graphics2D locg = (Graphics2D) tokenG.create( - (int) tokenBounds.getBounds().getX(), - (int) tokenBounds.getBounds().getY(), - (int) tokenBounds.getBounds().getWidth(), - (int) tokenBounds.getBounds().getHeight()); - Rectangle bounds = - new Rectangle( - 0, - 0, - (int) tokenBounds.getBounds().getWidth(), - (int) tokenBounds.getBounds().getHeight()); + (int) Math.floor(tokenBounds.getBounds().getCenterX() - bounds.getWidth() / 2.0), + (int) Math.floor(tokenBounds.getBounds().getCenterY() - bounds.getHeight() / 2.0), + bounds.width, + bounds.height); - // Check each of the set values + // Check each of the set values and draw for (String state : MapTool.getCampaign().getTokenStatesMap().keySet()) { Object stateValue = token.getState(state); AbstractTokenOverlay overlay = MapTool.getCampaign().getTokenStatesMap().get(state); From 0399b5b3dd0a6685da47b583277b6efd53180bcb Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:25:47 +0800 Subject: [PATCH 036/146] Minor tweak on setting token facing on non-rotated token for calculating bounds --- .../rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java index ed31e8ab86..18cd22ea0e 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java @@ -2601,7 +2601,7 @@ protected void renderTokens( * non-rotated token bounds to avoid odd size changes */ Token tmpToken = new Token(token); - tmpToken.setFacing(null); + tmpToken.setFacing(270); Rectangle tmpBounds = tmpToken.getBounds(this.zone); // Unless it is isometric, make it square to avoid distortion From 52b67a9393b8eeecfdcef032a3f03c24f8cf1df8 Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:05:46 +0800 Subject: [PATCH 037/146] Added ability to set hotkey and displayHotKey macro button properties to metamacro functions. --- .../client/functions/MacroFunctions.java | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java b/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java index b0357805fe..052f379765 100644 --- a/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java @@ -355,12 +355,16 @@ public void setMacroProps(MacroButtonProperties mbp, String propString, String d mbp.setAutoExecute(boolVal(value)); } else if ("color".equalsIgnoreCase(key)) { mbp.setColorKey(value); + } else if ("displayHotkey".equalsIgnoreCase(key)) { + mbp.setDisplayHotKey(boolVal(value)); } else if ("fontColor".equalsIgnoreCase(key)) { mbp.setFontColorKey(value); } else if ("fontSize".equalsIgnoreCase(key)) { mbp.setFontSize(value); } else if ("group".equalsIgnoreCase(key)) { mbp.setGroup(value); + } else if ("hotkey".equalsIgnoreCase(key)) { + mbp.setHotKey(value); } else if ("includeLabel".equalsIgnoreCase(key)) { mbp.setIncludeLabel(boolVal(value)); } else if ("sortBy".equalsIgnoreCase(key)) { @@ -1046,20 +1050,22 @@ private ImmutablePair getCampaignMbpByIndexOrLab */ private String macroButtonPropertiesToString(MacroButtonProperties mbp, String delim) { StringBuilder sb = new StringBuilder(); + sb.append("applyToSelected=").append(mbp.getApplyToTokens()).append(delim); sb.append("autoExecute=").append(mbp.getAutoExecute()).append(delim); sb.append("color=").append(mbp.getColorKey()).append(delim); + sb.append("displayHotkey=").append(mbp.getDisplayHotKey()).append(delim); sb.append("fontColor=").append(mbp.getFontColorKey()).append(delim); + sb.append("fontSize=").append(mbp.getFontSize()).append(delim); sb.append("group=").append(mbp.getGroup()).append(delim); + sb.append("hotkey=").append(mbp.getHotKey()).append(delim); sb.append("includeLabel=").append(mbp.getIncludeLabel()).append(delim); - sb.append("sortBy=").append(mbp.getSortby()).append(delim); sb.append("index=").append(mbp.getIndex()).append(delim); sb.append("label=").append(mbp.getLabel()).append(delim); - sb.append("fontSize=").append(mbp.getFontSize()).append(delim); + sb.append("maxWidth=").append(mbp.getMaxWidth()).append(delim); sb.append("minWidth=").append(mbp.getMinWidth()).append(delim); sb.append("playerEditable=").append(mbp.getAllowPlayerEdits()).append(delim); - sb.append("maxWidth=").append(mbp.getMaxWidth()).append(delim); + sb.append("sortBy=").append(mbp.getSortby()).append(delim); sb.append("tooltip=").append(mbp.getToolTip()).append(delim); - sb.append("applyToSelected=").append(mbp.getApplyToTokens()).append(delim); return sb.toString(); } @@ -1091,12 +1097,16 @@ private MacroButtonProperties macroButtonPropertiesFromJSON( mbp.setAutoExecute(boolVal(value)); } else if ("color".equalsIgnoreCase(key)) { mbp.setColorKey(value); + } else if ("displayHotkey".equalsIgnoreCase(key)) { + mbp.setDisplayHotKey(boolVal(value)); } else if ("fontColor".equalsIgnoreCase(key)) { mbp.setFontColorKey(value); } else if ("fontSize".equalsIgnoreCase(key)) { mbp.setFontSize(value); } else if ("group".equalsIgnoreCase(key)) { mbp.setGroup(value); + } else if ("hotkey".equalsIgnoreCase(key)) { + mbp.setHotKey(value); } else if ("includeLabel".equalsIgnoreCase(key)) { mbp.setIncludeLabel(boolVal(value)); } else if ("sortBy".equalsIgnoreCase(key)) { @@ -1184,21 +1194,23 @@ private MacroButtonProperties macroButtonPropertiesFromStringProp( */ private JsonObject macroButtonPropertiesToJSON(MacroButtonProperties mbp) { JsonObject props = new JsonObject(); + props.addProperty("applyToSelected", mbp.getApplyToTokens()); props.addProperty("autoExecute", mbp.getAutoExecute()); props.addProperty("color", mbp.getColorKey()); + props.addProperty("displayHotkey", mbp.getDisplayHotKey()); + props.addProperty("command", mbp.getCommand()); props.addProperty("fontColor", mbp.getFontColorKey()); + props.addProperty("fontSize", mbp.getFontSize()); props.addProperty("group", mbp.getGroup()); + props.addProperty("hotkey", mbp.getHotKey()); props.addProperty("includeLabel", mbp.getIncludeLabel()); - props.addProperty("sortBy", mbp.getSortby()); props.addProperty("index", mbp.getIndex()); props.addProperty("label", mbp.getLabel()); - props.addProperty("fontSize", mbp.getFontSize()); props.addProperty("minWidth", mbp.getMinWidth()); - props.addProperty("playerEditable", mbp.getAllowPlayerEdits()); - props.addProperty("command", mbp.getCommand()); props.addProperty("maxWidth", mbp.getMaxWidth()); + props.addProperty("playerEditable", mbp.getAllowPlayerEdits()); + props.addProperty("sortBy", mbp.getSortby()); props.addProperty("tooltip", mbp.getToolTip()); - props.addProperty("applyToSelected", mbp.getApplyToTokens()); JsonArray compare = new JsonArray(); if (mbp.getCompareGroup()) compare.add("group"); From 02c433fcc30fcdd92608b0a77dd39864b96df4dd Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:50:27 +0800 Subject: [PATCH 038/146] Added close on hitting escape key --- .../client/ui/exportdialog/ExportDialog.java | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/exportdialog/ExportDialog.java b/src/main/java/net/rptools/maptool/client/ui/exportdialog/ExportDialog.java index cbf494e71c..3a7eb279f4 100644 --- a/src/main/java/net/rptools/maptool/client/ui/exportdialog/ExportDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/exportdialog/ExportDialog.java @@ -19,7 +19,9 @@ import java.awt.Image; import java.awt.Rectangle; import java.awt.Transparency; +import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; @@ -33,11 +35,7 @@ import javax.imageio.ImageIO; import javax.imageio.ImageWriter; import javax.imageio.event.IIOWriteProgressListener; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JFileChooser; -import javax.swing.JLabel; -import javax.swing.JToggleButton; +import javax.swing.*; import net.rptools.lib.net.FTPLocation; import net.rptools.lib.net.LocalLocation; import net.rptools.lib.net.Location; @@ -64,6 +62,13 @@ * the 'board' image/tile. The file can be saved to disk or sent to an FTP location. */ public class ExportDialog extends JDialog implements IIOWriteProgressListener { + public enum Status { + OK, + CANCEL + } + + private ExportDialog.Status status; + // // Dialog/ UI related vars // @@ -426,6 +431,20 @@ private ExportDialog() { interactPanel.getButton("exportButton").addActionListener(evt -> exportButtonAction()); interactPanel.getButton("cancelButton").addActionListener(evt -> dispose()); interactPanel.getButton("browseButton").addActionListener(evt -> browseButtonAction()); + + // Escape key + interactPanel + .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + .put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel"); + interactPanel + .getActionMap() + .put( + "cancel", + new AbstractAction() { + public void actionPerformed(ActionEvent e) { + cancel(); + } + }); } @Override @@ -450,6 +469,11 @@ public void setVisible(boolean b) { super.setVisible(b); } + private void cancel() { + status = ExportDialog.Status.CANCEL; + setVisible(false); + } + // // These get/set the convenience variables zone and renderer // From 7de21700a2b746559b1a584983894998f8abb26f Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:39:12 +0800 Subject: [PATCH 039/146] Changed behaviour of adding token property. When there is a table row selected, new property is inserted at that row instead of being appended. --- .../TokenPropertiesManagementPanel.java | 1580 +++++++++-------- .../TokenPropertiesTableModel.java | 11 +- 2 files changed, 800 insertions(+), 791 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanel.java b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanel.java index 29b501a8ba..659cb0ced3 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanel.java @@ -40,817 +40,821 @@ import org.apache.logging.log4j.Logger; public class TokenPropertiesManagementPanel extends AbeillePanel { - private static final Logger log = LogManager.getLogger(TokenPropertiesManagementPanel.class); - private Map> tokenTypeMap; - private final Map tokenTypeStatSheetMap = new HashMap<>(); - private String editingType; - - private final SortedMap renameTypes = new TreeMap<>(); - - private String defaultPropertyType; - - CampaignProperties campaignProperties; - - public TokenPropertiesManagementPanel() { - super(new TokenPropertiesManagementPanelView().getRootComponent()); - - panelInit(); - } - - public void copyCampaignToUI(CampaignProperties cp) { - campaignProperties = cp; - defaultPropertyType = cp.getDefaultTokenPropertyType(); - - tokenTypeMap = new HashMap<>(); - campaignProperties - .getTokenTypeMap() - .forEach( - (k, v) -> - tokenTypeMap.put(k, new ArrayList<>(v.stream().map(TokenProperty::new).toList()))); - var ssManager = new StatSheetManager(); - tokenTypeMap - .keySet() - .forEach( - tt -> - tokenTypeStatSheetMap.put(tt, campaignProperties.getTokenTypeDefaultStatSheet(tt))); - updateTypeList(); - } - - public void copyUIToCampaign(Campaign campaign) { - - campaign.getTokenTypeMap().clear(); - campaign.getTokenTypeMap().putAll(tokenTypeMap); - campaign - .getTokenTypeMap() - .keySet() - .forEach(tt -> campaign.setTokenTypeDefaultSheetId(tt, tokenTypeStatSheetMap.get(tt))); - campaign.setDefaultTokenPropertyType(defaultPropertyType); - } - - public void finalizeCellEditing() { - if (getTokenPropertiesTable().isEditing()) { - getTokenPropertiesTable().getCellEditor().stopCellEditing(); - } - } - - public JList getTokenTypeList() { - JList list = (JList) getComponent("tokenTypeList"); - if (list == null) { - list = new JList(); - } - return list; - } - - public JTextField getTokenTypeName() { - return (JTextField) getComponent("tokenTypeName"); - } - - public JButton getTypeAddButton() { - return (JButton) getComponent("typeAddButton"); - } - - public JButton getTypeDeleteButton() { - return (JButton) getComponent("typeDeleteButton"); - } - - public JButton getTypeDuplicateButton() { - return (JButton) getComponent("typeDuplicateButton"); - } - - public JButton getPropertyMoveUpButton() { - return (JButton) getComponent("propertyMoveUpButton"); - } - - public JButton getPropertyMoveDownButton() { - return (JButton) getComponent("propertyMoveDownButton"); - } - - public JButton getPropertyAddButton() { - return (JButton) getComponent("propertyAddButton"); - } - - public JButton getPropertyDeleteButton() { - return (JButton) getComponent("propertyDeleteButton"); - } - - public JTable getTokenPropertiesTable() { - return (JTable) getComponent("propertiesTable"); - } - - public JComboBox getStatSheetLocationComboBox() { - return (JComboBox) getComponent("statSheetLocationComboBox"); - } - - public JComboBox getStatSheetComboBox() { - return (JComboBox) getComponent("statSheetComboBox"); - } - - public JButton getTypeSetAsDefault() { - return (JButton) getComponent("typeDefaultButton"); - } - - public JButton getHelpButton() { - return (JButton) getComponent("helpButton"); - } - - public JPanel getDescriptionContainer() { - return (JPanel) getComponent("descriptionContainer"); - } - - public TokenPropertiesTableModel getTokenPropertiesTableModel() { - return (TokenPropertiesTableModel) getTokenPropertiesTable().getModel(); - } - - public JScrollPane getTableScrollPane() { - return (JScrollPane) getComponent("tokenPropertiesTableScrollPane"); - } - - public void initTypeAddButton() { - getTypeAddButton() - .addActionListener( - e -> - EventQueue.invokeLater( - () -> { - // First find a unique name, there are so few entries we don't have to worry - // about being fancy - int seq = 1; - String name = - I18N.getText("campaignPropertiesDialog.newTokenTypeDefaultName", seq); - while (tokenTypeMap.containsKey(name)) { - seq++; - name = - I18N.getText("campaignPropertiesDialog.newTokenTypeDefaultName", seq); - } - - var newName = - (String) - JOptionPane.showInputDialog( - this, - I18N.getText("campaignPropertiesDialog.newTokenTypeName"), - I18N.getText("campaignPropertiesDialog.newTokenTypeTitle"), - JOptionPane.PLAIN_MESSAGE, - null, - null, - name); - if (newName != null) { - tokenTypeMap.put(newName, new LinkedList<>()); - updateTypeList(); - getTokenTypeList().setSelectedValue(newName, true); - } - })); - } - - public void initTypeDeleteButton() { - var button = getTypeDeleteButton(); - button.addActionListener( - e -> { - var type = (String) getTokenTypeList().getSelectedValue(); - if (type != null) { - JPanel renameToPanel = new JPanel(); - JComboBox types = - new JComboBox<>( - tokenTypeMap.keySet().stream() - .filter(t -> !t.equals(type)) - .map(String::toString) - .toArray(String[]::new)); - renameToPanel.add( - new JLabel(I18N.getText("campaignPropertiesDialog.tokenTypeNameDeleteMessage"))); - renameToPanel.add(types); - int option = - JOptionPane.showConfirmDialog( - this, - renameToPanel, - I18N.getText("campaignPropertiesDialog.tokenTypeNameDeleteTitle", type), - JOptionPane.OK_CANCEL_OPTION); - if (option == JOptionPane.OK_OPTION) { - var newType = (String) types.getSelectedItem(); - if (newType != null) { - renameTypes.put(type, newType); - tokenTypeMap.remove(type); - updateTypeList(); - } - } - } - }); - button.setEnabled(false); - } - - public void initTypeDefaultButton() { - var button = getTypeSetAsDefault(); - button.addActionListener( - l -> { - var propertyType = (String) getTokenTypeList().getSelectedValue(); - if (propertyType != null) { - defaultPropertyType = propertyType; - button.setEnabled(false); - var delButton = getTypeDeleteButton(); - delButton.setEnabled(false); - } - }); - - button.setEnabled(false); - } - - public void initPropertyMoveUpButton() { - var button = getPropertyMoveUpButton(); - button.addActionListener( - l -> { - finalizeCellEditing(); - JTable propertiesTable = getTokenPropertiesTable(); - var selectedRow = propertiesTable.getSelectedRow(); - if (selectedRow <= 0) { - return; - } - - var model = getTokenPropertiesTableModel(); - model.movePropertyUp(selectedRow); - --selectedRow; - propertiesTable.setRowSelectionInterval(selectedRow, selectedRow); - propertiesTable.scrollRectToVisible(propertiesTable.getCellRect(selectedRow, 0, true)); - }); - button.setEnabled(false); - } - - public void initPropertyMoveDownButton() { - var button = getPropertyMoveDownButton(); - button.addActionListener( - l -> { - finalizeCellEditing(); - JTable propertiesTable = getTokenPropertiesTable(); - var selectedRow = propertiesTable.getSelectedRow(); - if (selectedRow < 0 || selectedRow >= propertiesTable.getRowCount() - 1) { - return; - } - - var model = getTokenPropertiesTableModel(); - model.movePropertyDown(selectedRow); - ++selectedRow; - propertiesTable.setRowSelectionInterval(selectedRow, selectedRow); - propertiesTable.scrollRectToVisible(propertiesTable.getCellRect(selectedRow, 0, true)); - }); - button.setEnabled(false); - } - - public void initPropertyAddButton() { - var button = getPropertyAddButton(); - button.addActionListener( - e -> - EventQueue.invokeLater( - () -> { - finalizeCellEditing(); - JTable propertiesTable = getTokenPropertiesTable(); - var model = getTokenPropertiesTableModel(); - model.addProperty(); - int count = model.getRowCount(); - propertiesTable.scrollRectToVisible( - propertiesTable.getCellRect(count - 1, 0, true)); - })); - button.setEnabled(false); - } - - public void initPropertyDeleteButton() { - var button = getPropertyDeleteButton(); - button.addActionListener( - e -> - EventQueue.invokeLater( - () -> { - finalizeCellEditing(); - var model = getTokenPropertiesTableModel(); - model.deleteProperty(getTokenPropertiesTable().getSelectedRow()); - })); - button.setEnabled(false); - } - - public void initTypeDuplicateButton() { - var button = getTypeDuplicateButton(); - button.addActionListener( - e -> - EventQueue.invokeLater( - () -> { - log.info("Type Duplicate - button action"); - var propertyType = (String) getTokenTypeList().getSelectedValue(); - if (propertyType != null) { - String newName = propertyType + "@"; - tokenTypeMap.put(newName, tokenTypeMap.get(propertyType)); - updateTypeList(); - getTokenTypeList().setSelectedValue(newName, true); - button.setEnabled(true); - } - })); - button.setEnabled(false); - } - - public void initDescriptionContainer() { - getDescriptionContainer().setVisible(false); - } - - public void initHelpButton() { - var button = getHelpButton(); - button.addActionListener( - e -> - EventQueue.invokeLater( - () -> { - JPanel helpText = getDescriptionContainer(); - helpText.setVisible(!helpText.isVisible()); - })); - button.setEnabled(true); - } - - public void initPropertyTable() { - var propertyTable = getTokenPropertiesTable(); - propertyTable.setModel(new TokenPropertiesTableModel()); - propertyTable.setDefaultEditor( - LargeEditableText.class, new TextFieldEditorButtonTableCellEditor()); - propertyTable - .getSelectionModel() - .addListSelectionListener( - e -> { - if (e.getValueIsAdjusting()) { - return; - } - - var deleteButton = getPropertyDeleteButton(); - deleteButton.setEnabled(getTokenPropertiesTable().getSelectedRow() >= 0); - - var moveUpButton = getPropertyMoveUpButton(); - moveUpButton.setEnabled(getTokenPropertiesTable().getSelectedRow() > 0); - - var moveDownButton = getPropertyMoveDownButton(); - // Note: this works even if selection is empty (getSelectedRow() == -1). - moveDownButton.setEnabled( - getTokenPropertiesTable().getSelectedRow() - < getTokenPropertiesTable().getRowCount() - 1); - }); - } - - public void initTokenTypeName() { - var field = getTokenTypeName(); - field.setEditable(false); - field.addActionListener( - event -> { - int option = - JOptionPane.showConfirmDialog( - this, - I18N.getText("campaignPropertiesDialog.tokenTypeNameChangeWarning"), - I18N.getText("campaignPropertiesDialog.tokenTypeNameChangeTitle"), - JOptionPane.OK_CANCEL_OPTION, - JOptionPane.WARNING_MESSAGE); - if (option == JOptionPane.OK_OPTION) { - var ttList = getTokenTypeList(); - var oldName = (String) ttList.getSelectedValue(); - var newName = field.getText(); - tokenTypeMap.put(newName, tokenTypeMap.remove(oldName)); - tokenTypeStatSheetMap.put(newName, tokenTypeStatSheetMap.remove(oldName)); - ttList.setSelectedValue(newName, true); - updateExistingTokenTypes(oldName, newName); - } - }); - } - - private void updateExistingTokenTypes(String oldName, String newName) { - if (oldName == null || newName == null || oldName.equals(newName)) { - return; - } - if (defaultPropertyType.equals(oldName)) { - defaultPropertyType = newName; - } - renameTypes.put(oldName, newName); - } - - public void initTypeList() { - - getTokenTypeList() - .addListSelectionListener( - e -> { - if (e.getValueIsAdjusting()) { - return; - } - - var propertyType = - getTokenTypeList().getSelectedValue() == null - ? null - : getTokenTypeList().getSelectedValue().toString(); - - finalizeCellEditing(); - - if (propertyType == null) { - reset(); - getPropertyAddButton().setEnabled(false); - getTypeDeleteButton().setEnabled(false); - getTypeDuplicateButton().setEnabled(false); - getTokenTypeName().setEditable(false); - getStatSheetComboBox().setEnabled(false); - getStatSheetLocationComboBox().setEnabled(false); - getTypeSetAsDefault().setEnabled(false); - } else { - bind((String) getTokenTypeList().getSelectedValue()); - getPropertyAddButton().setEnabled(true); - getTypeDuplicateButton().setEnabled(true); - getTokenTypeName().setEditable(true); - // Can't delete the default property - if (propertyType.equals(defaultPropertyType)) { - getTypeDeleteButton().setEnabled(false); - } else { - getTypeDeleteButton().setEnabled(true); - } - getStatSheetComboBox().setEnabled(true); - getStatSheetLocationComboBox().setEnabled(true); - populateStatSheetComboBoxes(propertyType); - if (!propertyType.equals(defaultPropertyType)) { - getTypeSetAsDefault().setEnabled(true); - } else { - getTypeSetAsDefault().setEnabled(false); - } - } - }); - getTokenTypeList().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - getTokenTypeList().setCellRenderer(new TokenTypeCellRenderer()); - } - - private void populateStatSheetComboBoxes(String propertyType) { - var combo = getStatSheetComboBox(); - combo.removeAllItems(); - var ssManager = new StatSheetManager(); - ssManager.getStatSheets(propertyType).stream() - .sorted(Comparator.comparing(StatSheet::description)) - .forEach(ss -> combo.addItem(ss)); - var statSheetProperty = tokenTypeStatSheetMap.get(propertyType); - String id; - if (statSheetProperty == null) { - id = ssManager.getDefaultStatSheetId(); - tokenTypeStatSheetMap.put( - propertyType, - new StatSheetProperties( - ssManager.getDefaultStatSheetId(), StatSheetLocation.BOTTOM_LEFT)); - } else { - id = statSheetProperty.id(); - } - combo.setSelectedItem(ssManager.getStatSheet(id)); - - var locationCombo = getStatSheetLocationComboBox(); - locationCombo.setSelectedItem(tokenTypeStatSheetMap.get(propertyType).location()); - } - - public void initStatSheetDetails() { - var locationCombo = getStatSheetLocationComboBox(); - locationCombo.setEnabled(false); - Arrays.stream(StatSheetLocation.values()).forEach(locationCombo::addItem); - locationCombo.addActionListener( - l -> { - if (getStatSheetLocationComboBox().hasFocus()) { // only if user has made change - var location = (StatSheetLocation) locationCombo.getSelectedItem(); - var tokenType = (String) getTokenTypeList().getSelectedValue(); - if (location != null && tokenType != null) { - var id = tokenTypeStatSheetMap.get(tokenType).id(); - tokenTypeStatSheetMap.put(tokenType, new StatSheetProperties(id, location)); - } - } - }); - - var combo = getStatSheetComboBox(); - combo.setEnabled(false); - combo.setRenderer(new StatSheetComboBoxRenderer()); - combo.addActionListener( - l -> { - if (getStatSheetComboBox().hasFocus()) { // Only if user has made change - var ss = (StatSheet) combo.getSelectedItem(); - var tokenType = (String) getTokenTypeList().getSelectedValue(); - if (ss != null && tokenType != null) { - var id = new StatSheetManager().getId(ss); - var location = tokenTypeStatSheetMap.get(tokenType).location(); - tokenTypeStatSheetMap.put(tokenType, new StatSheetProperties(id, location)); - getStatSheetLocationComboBox().setSelectedItem(location); - } - } - }); - } - - private void bind(String type) { - - editingType = type; - - getTokenTypeName().setText(type != null ? type : ""); - var model = getTokenPropertiesTableModel(); - model.setPropertyType(type); - } - - private void reset() { - - bind((String) null); - } - - private void updateTypeList() { - getTokenTypeList().setModel(new TypeListModel()); - getTokenPropertiesTableModel().setPropertyTypeMap(tokenTypeMap); - } - - private String compileTokenProperties(List propertyList) { - - // Sanity - if (propertyList == null) { - return ""; - } - - StringBuilder builder = new StringBuilder(); - - for (TokenProperty property : propertyList) { - if (property.isShowOnStatSheet()) { - builder.append("*"); - } - if (property.isOwnerOnly()) { - builder.append("@"); - } - if (property.isGMOnly()) { - builder.append("#"); - } - builder.append(property.getName()); - if (property.getShortName() != null) { - builder.append(" (").append(property.getShortName()).append(")"); - } - if (property.getDefaultValue() != null) { - builder.append(":").append(property.getDefaultValue()); - } - builder.append("\n"); - } - - return builder.toString(); - } - - /** - * Given a string (normally from the JTextArea which holds the properties for a Property Type) - * this method converts those lines into a List of EditTokenProperty objects. It checks for - * duplicates along the way, ignoring any it finds. (Should produce a list of warnings to indicate - * which ones are duplicates. See the Light/Sight code for examples.) - * - * @param propertyText - * @return - */ - private List parseTokenProperties(String propertyText) - throws IllegalArgumentException { - List propertyList = new ArrayList(); - BufferedReader reader = new BufferedReader(new StringReader(propertyText)); - CaseInsensitiveHashMap caseCheck = new CaseInsensitiveHashMap(); - List errlog = new LinkedList(); - try { - String original, line; - while ((original = reader.readLine()) != null) { - line = original = original.trim(); - if (line.length() == 0) { - continue; + private static final Logger log = LogManager.getLogger(TokenPropertiesManagementPanel.class); + private Map> tokenTypeMap; + private final Map tokenTypeStatSheetMap = new HashMap<>(); + private String editingType; + + private final SortedMap renameTypes = new TreeMap<>(); + + private String defaultPropertyType; + + CampaignProperties campaignProperties; + + public TokenPropertiesManagementPanel() { + super(new TokenPropertiesManagementPanelView().getRootComponent()); + + panelInit(); + } + + public void copyCampaignToUI(CampaignProperties cp) { + campaignProperties = cp; + defaultPropertyType = cp.getDefaultTokenPropertyType(); + + tokenTypeMap = new HashMap<>(); + campaignProperties + .getTokenTypeMap() + .forEach( + (k, v) -> + tokenTypeMap.put(k, new ArrayList<>(v.stream().map(TokenProperty::new).toList()))); + var ssManager = new StatSheetManager(); + tokenTypeMap + .keySet() + .forEach( + tt -> + tokenTypeStatSheetMap.put(tt, campaignProperties.getTokenTypeDefaultStatSheet(tt))); + updateTypeList(); + } + + public void copyUIToCampaign(Campaign campaign) { + + campaign.getTokenTypeMap().clear(); + campaign.getTokenTypeMap().putAll(tokenTypeMap); + campaign + .getTokenTypeMap() + .keySet() + .forEach(tt -> campaign.setTokenTypeDefaultSheetId(tt, tokenTypeStatSheetMap.get(tt))); + campaign.setDefaultTokenPropertyType(defaultPropertyType); + } + + public void finalizeCellEditing() { + if (getTokenPropertiesTable().isEditing()) { + getTokenPropertiesTable().getCellEditor().stopCellEditing(); } + } - TokenProperty property = new TokenProperty(); - - // Prefix - while (true) { - if (line.startsWith("*")) { - property.setShowOnStatSheet(true); - line = line.substring(1); - continue; - } - if (line.startsWith("@")) { - property.setOwnerOnly(true); - line = line.substring(1); - continue; - } - if (line.startsWith("#")) { - property.setGMOnly(true); - line = line.substring(1); - continue; - } - - // Ran out of special characters - break; + public JList getTokenTypeList() { + JList list = (JList) getComponent("tokenTypeList"); + if (list == null) { + list = new JList(); } + return list; + } + + public JTextField getTokenTypeName() { + return (JTextField) getComponent("tokenTypeName"); + } + + public JButton getTypeAddButton() { + return (JButton) getComponent("typeAddButton"); + } + + public JButton getTypeDeleteButton() { + return (JButton) getComponent("typeDeleteButton"); + } + + public JButton getTypeDuplicateButton() { + return (JButton) getComponent("typeDuplicateButton"); + } + + public JButton getPropertyMoveUpButton() { + return (JButton) getComponent("propertyMoveUpButton"); + } + + public JButton getPropertyMoveDownButton() { + return (JButton) getComponent("propertyMoveDownButton"); + } + + public JButton getPropertyAddButton() { + return (JButton) getComponent("propertyAddButton"); + } + + public JButton getPropertyDeleteButton() { + return (JButton) getComponent("propertyDeleteButton"); + } + + public JTable getTokenPropertiesTable() { + return (JTable) getComponent("propertiesTable"); + } + + public JComboBox getStatSheetLocationComboBox() { + return (JComboBox) getComponent("statSheetLocationComboBox"); + } + + public JComboBox getStatSheetComboBox() { + return (JComboBox) getComponent("statSheetComboBox"); + } + + public JButton getTypeSetAsDefault() { + return (JButton) getComponent("typeDefaultButton"); + } + + public JButton getHelpButton() { + return (JButton) getComponent("helpButton"); + } + + public JPanel getDescriptionContainer() { + return (JPanel) getComponent("descriptionContainer"); + } + + public TokenPropertiesTableModel getTokenPropertiesTableModel() { + return (TokenPropertiesTableModel) getTokenPropertiesTable().getModel(); + } + + public JScrollPane getTableScrollPane() { + return (JScrollPane) getComponent("tokenPropertiesTableScrollPane"); + } + + public void initTypeAddButton() { + getTypeAddButton() + .addActionListener( + e -> + EventQueue.invokeLater( + () -> { + // First find a unique name, there are so few entries we don't have to worry + // about being fancy + int seq = 1; + String name = + I18N.getText("campaignPropertiesDialog.newTokenTypeDefaultName", seq); + while (tokenTypeMap.containsKey(name)) { + seq++; + name = + I18N.getText("campaignPropertiesDialog.newTokenTypeDefaultName", seq); + } + + var newName = + (String) + JOptionPane.showInputDialog( + this, + I18N.getText("campaignPropertiesDialog.newTokenTypeName"), + I18N.getText("campaignPropertiesDialog.newTokenTypeTitle"), + JOptionPane.PLAIN_MESSAGE, + null, + null, + name); + if (newName != null) { + tokenTypeMap.put(newName, new LinkedList<>()); + updateTypeList(); + getTokenTypeList().setSelectedValue(newName, true); + } + })); + } + + public void initTypeDeleteButton() { + var button = getTypeDeleteButton(); + button.addActionListener( + e -> { + var type = (String) getTokenTypeList().getSelectedValue(); + if (type != null) { + JPanel renameToPanel = new JPanel(); + JComboBox types = + new JComboBox<>( + tokenTypeMap.keySet().stream() + .filter(t -> !t.equals(type)) + .map(String::toString) + .toArray(String[]::new)); + renameToPanel.add( + new JLabel(I18N.getText("campaignPropertiesDialog.tokenTypeNameDeleteMessage"))); + renameToPanel.add(types); + int option = + JOptionPane.showConfirmDialog( + this, + renameToPanel, + I18N.getText("campaignPropertiesDialog.tokenTypeNameDeleteTitle", type), + JOptionPane.OK_CANCEL_OPTION); + if (option == JOptionPane.OK_OPTION) { + var newType = (String) types.getSelectedItem(); + if (newType != null) { + renameTypes.put(type, newType); + tokenTypeMap.remove(type); + updateTypeList(); + } + } + } + }); + button.setEnabled(false); + } + + public void initTypeDefaultButton() { + var button = getTypeSetAsDefault(); + button.addActionListener( + l -> { + var propertyType = (String) getTokenTypeList().getSelectedValue(); + if (propertyType != null) { + defaultPropertyType = propertyType; + button.setEnabled(false); + var delButton = getTypeDeleteButton(); + delButton.setEnabled(false); + } + }); + + button.setEnabled(false); + } + + public void initPropertyMoveUpButton() { + var button = getPropertyMoveUpButton(); + button.addActionListener( + l -> { + finalizeCellEditing(); + JTable propertiesTable = getTokenPropertiesTable(); + var selectedRow = propertiesTable.getSelectedRow(); + if (selectedRow <= 0) { + return; + } + + var model = getTokenPropertiesTableModel(); + model.movePropertyUp(selectedRow); + --selectedRow; + propertiesTable.setRowSelectionInterval(selectedRow, selectedRow); + propertiesTable.scrollRectToVisible(propertiesTable.getCellRect(selectedRow, 0, true)); + }); + button.setEnabled(false); + } + + public void initPropertyMoveDownButton() { + var button = getPropertyMoveDownButton(); + button.addActionListener( + l -> { + finalizeCellEditing(); + JTable propertiesTable = getTokenPropertiesTable(); + var selectedRow = propertiesTable.getSelectedRow(); + if (selectedRow < 0 || selectedRow >= propertiesTable.getRowCount() - 1) { + return; + } + + var model = getTokenPropertiesTableModel(); + model.movePropertyDown(selectedRow); + ++selectedRow; + propertiesTable.setRowSelectionInterval(selectedRow, selectedRow); + propertiesTable.scrollRectToVisible(propertiesTable.getCellRect(selectedRow, 0, true)); + }); + button.setEnabled(false); + } + + public void initPropertyAddButton() { + var button = getPropertyAddButton(); + button.addActionListener( + e -> + EventQueue.invokeLater( + () -> { + finalizeCellEditing(); + JTable propertiesTable = getTokenPropertiesTable(); + var model = getTokenPropertiesTableModel(); + // selected row is -1 for no selection causing property to be appended to list instead of inserted + int selectedRow = propertiesTable.getSelectedRow(); + model.addProperty(selectedRow); + int count = model.getRowCount(); + propertiesTable.scrollRectToVisible( + propertiesTable.getCellRect( + selectedRow == -1 ? count - 1 : selectedRow, 0, true)); + propertiesTable.repaint(); + })); + button.setEnabled(false); + } + + public void initPropertyDeleteButton() { + var button = getPropertyDeleteButton(); + button.addActionListener( + e -> + EventQueue.invokeLater( + () -> { + finalizeCellEditing(); + var model = getTokenPropertiesTableModel(); + model.deleteProperty(getTokenPropertiesTable().getSelectedRow()); + })); + button.setEnabled(false); + } + + public void initTypeDuplicateButton() { + var button = getTypeDuplicateButton(); + button.addActionListener( + e -> + EventQueue.invokeLater( + () -> { + log.info("Type Duplicate - button action"); + var propertyType = (String) getTokenTypeList().getSelectedValue(); + if (propertyType != null) { + String newName = propertyType + "@"; + tokenTypeMap.put(newName, tokenTypeMap.get(propertyType)); + updateTypeList(); + getTokenTypeList().setSelectedValue(newName, true); + button.setEnabled(true); + } + })); + button.setEnabled(false); + } - // default value - // had to do this here since the short name is not built - // to take advantage of multiple opening/closing parenthesis - // in a single property line - int indexDefault = line.indexOf(':'); - if (indexDefault > 0) { - String defaultVal = line.substring(indexDefault + 1).trim(); - if (defaultVal.length() > 0) { - property.setDefaultValue(defaultVal); - } - - // remove the default value from the end of the string... - line = line.substring(0, indexDefault); + public void initDescriptionContainer() { + getDescriptionContainer().setVisible(false); + } + + public void initHelpButton() { + var button = getHelpButton(); + button.addActionListener( + e -> + EventQueue.invokeLater( + () -> { + JPanel helpText = getDescriptionContainer(); + helpText.setVisible(!helpText.isVisible()); + })); + button.setEnabled(true); + } + + public void initPropertyTable() { + var propertyTable = getTokenPropertiesTable(); + propertyTable.setModel(new TokenPropertiesTableModel()); + propertyTable.setDefaultEditor( + LargeEditableText.class, new TextFieldEditorButtonTableCellEditor()); + propertyTable + .getSelectionModel() + .addListSelectionListener( + e -> { + if (e.getValueIsAdjusting()) { + return; + } + + var deleteButton = getPropertyDeleteButton(); + deleteButton.setEnabled(getTokenPropertiesTable().getSelectedRow() >= 0); + + var moveUpButton = getPropertyMoveUpButton(); + moveUpButton.setEnabled(getTokenPropertiesTable().getSelectedRow() > 0); + + var moveDownButton = getPropertyMoveDownButton(); + // Note: this works even if selection is empty (getSelectedRow() == -1). + moveDownButton.setEnabled( + getTokenPropertiesTable().getSelectedRow() + < getTokenPropertiesTable().getRowCount() - 1); + }); + } + + public void initTokenTypeName() { + var field = getTokenTypeName(); + field.setEditable(false); + field.addActionListener( + event -> { + int option = + JOptionPane.showConfirmDialog( + this, + I18N.getText("campaignPropertiesDialog.tokenTypeNameChangeWarning"), + I18N.getText("campaignPropertiesDialog.tokenTypeNameChangeTitle"), + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE); + if (option == JOptionPane.OK_OPTION) { + var ttList = getTokenTypeList(); + var oldName = (String) ttList.getSelectedValue(); + var newName = field.getText(); + tokenTypeMap.put(newName, tokenTypeMap.remove(oldName)); + tokenTypeStatSheetMap.put(newName, tokenTypeStatSheetMap.remove(oldName)); + ttList.setSelectedValue(newName, true); + updateExistingTokenTypes(oldName, newName); + } + }); + } + + private void updateExistingTokenTypes(String oldName, String newName) { + if (oldName == null || newName == null || oldName.equals(newName)) { + return; } - // Suffix - // (Really should handle nested parens here) - int index = line.indexOf('('); - if (index > 0) { - int indexClose = line.lastIndexOf(')'); - // Check for unenclosed parentheses. Fix #1575. - if (indexClose < index) { - MapTool.showError(I18N.getText("CampaignPropertyDialog.error.parenthesis", line)); - throw new IllegalArgumentException("Missing parenthesis"); - } - String shortName = line.substring(index + 1, indexClose).trim(); - if (shortName.length() > 0) { - property.setShortName(shortName); - } - line = line.substring(0, index); + if (defaultPropertyType.equals(oldName)) { + defaultPropertyType = newName; } - line = line.trim(); - property.setName(line); - // Since property names are not case-sensitive, let's make sure that we don't - // already have this name represented somewhere in the list. - String old = caseCheck.get(line); - if (old != null) { - // Perhaps these properties should produce warnings at all, but what it someone - // is actually using them as property names! - if (old.startsWith("---")) - errlog.add( - I18N.getText("msg.error.mtprops.properties.duplicateComment", original, old)); - else errlog.add(I18N.getText("msg.error.mtprops.properties.duplicate", original, old)); + renameTypes.put(oldName, newName); + } + + public void initTypeList() { + + getTokenTypeList() + .addListSelectionListener( + e -> { + if (e.getValueIsAdjusting()) { + return; + } + + var propertyType = + getTokenTypeList().getSelectedValue() == null + ? null + : getTokenTypeList().getSelectedValue().toString(); + + finalizeCellEditing(); + + if (propertyType == null) { + reset(); + getPropertyAddButton().setEnabled(false); + getTypeDeleteButton().setEnabled(false); + getTypeDuplicateButton().setEnabled(false); + getTokenTypeName().setEditable(false); + getStatSheetComboBox().setEnabled(false); + getStatSheetLocationComboBox().setEnabled(false); + getTypeSetAsDefault().setEnabled(false); + } else { + bind((String) getTokenTypeList().getSelectedValue()); + getPropertyAddButton().setEnabled(true); + getTypeDuplicateButton().setEnabled(true); + getTokenTypeName().setEditable(true); + // Can't delete the default property + if (propertyType.equals(defaultPropertyType)) { + getTypeDeleteButton().setEnabled(false); + } else { + getTypeDeleteButton().setEnabled(true); + } + getStatSheetComboBox().setEnabled(true); + getStatSheetLocationComboBox().setEnabled(true); + populateStatSheetComboBoxes(propertyType); + if (!propertyType.equals(defaultPropertyType)) { + getTypeSetAsDefault().setEnabled(true); + } else { + getTypeSetAsDefault().setEnabled(false); + } + } + }); + getTokenTypeList().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + getTokenTypeList().setCellRenderer(new TokenTypeCellRenderer()); + } + + private void populateStatSheetComboBoxes(String propertyType) { + var combo = getStatSheetComboBox(); + combo.removeAllItems(); + var ssManager = new StatSheetManager(); + ssManager.getStatSheets(propertyType).stream() + .sorted(Comparator.comparing(StatSheet::description)) + .forEach(ss -> combo.addItem(ss)); + var statSheetProperty = tokenTypeStatSheetMap.get(propertyType); + String id; + if (statSheetProperty == null) { + id = ssManager.getDefaultStatSheetId(); + tokenTypeStatSheetMap.put( + propertyType, + new StatSheetProperties( + ssManager.getDefaultStatSheetId(), StatSheetLocation.BOTTOM_LEFT)); } else { - propertyList.add(property); - caseCheck.put(line, original); + id = statSheetProperty.id(); } - } + combo.setSelectedItem(ssManager.getStatSheet(id)); - } catch (IOException ioe) { - // If this happens, I'll check into the nearest insane asylum - MapTool.showError("IOException during parsing of properties?!", ioe); + var locationCombo = getStatSheetLocationComboBox(); + locationCombo.setSelectedItem(tokenTypeStatSheetMap.get(propertyType).location()); } - caseCheck.clear(); - if (!errlog.isEmpty()) { - errlog.add(0, I18N.getText("msg.error.mtprops.properties.title", editingType)); - errlog.add(I18N.getText("msg.error.mtprops.properties.ending")); - MapTool.showFeedback(errlog.toArray()); - errlog.clear(); - throw new IllegalArgumentException(); // Don't save the properties... + + public void initStatSheetDetails() { + var locationCombo = getStatSheetLocationComboBox(); + locationCombo.setEnabled(false); + Arrays.stream(StatSheetLocation.values()).forEach(locationCombo::addItem); + locationCombo.addActionListener( + l -> { + if (getStatSheetLocationComboBox().hasFocus()) { // only if user has made change + var location = (StatSheetLocation) locationCombo.getSelectedItem(); + var tokenType = (String) getTokenTypeList().getSelectedValue(); + if (location != null && tokenType != null) { + var id = tokenTypeStatSheetMap.get(tokenType).id(); + tokenTypeStatSheetMap.put(tokenType, new StatSheetProperties(id, location)); + } + } + }); + + var combo = getStatSheetComboBox(); + combo.setEnabled(false); + combo.setRenderer(new StatSheetComboBoxRenderer()); + combo.addActionListener( + l -> { + if (getStatSheetComboBox().hasFocus()) { // Only if user has made change + var ss = (StatSheet) combo.getSelectedItem(); + var tokenType = (String) getTokenTypeList().getSelectedValue(); + if (ss != null && tokenType != null) { + var id = new StatSheetManager().getId(ss); + var location = tokenTypeStatSheetMap.get(tokenType).location(); + tokenTypeStatSheetMap.put(tokenType, new StatSheetProperties(id, location)); + getStatSheetLocationComboBox().setSelectedItem(location); + } + } + }); + } + + private void bind(String type) { + + editingType = type; + + getTokenTypeName().setText(type != null ? type : ""); + var model = getTokenPropertiesTableModel(); + model.setPropertyType(type); } - return propertyList; - } - public void prettify() { + private void reset() { + + bind((String) null); + } + + private void updateTypeList() { + getTokenTypeList().setModel(new TypeListModel()); + getTokenPropertiesTableModel().setPropertyTypeMap(tokenTypeMap); + } + + private String compileTokenProperties(List propertyList) { + + // Sanity + if (propertyList == null) { + return ""; + } + + StringBuilder builder = new StringBuilder(); + + for (TokenProperty property : propertyList) { + if (property.isShowOnStatSheet()) { + builder.append("*"); + } + if (property.isOwnerOnly()) { + builder.append("@"); + } + if (property.isGMOnly()) { + builder.append("#"); + } + builder.append(property.getName()); + if (property.getShortName() != null) { + builder.append(" (").append(property.getShortName()).append(")"); + } + if (property.getDefaultValue() != null) { + builder.append(":").append(property.getDefaultValue()); + } + builder.append("\n"); + } + + return builder.toString(); + } - /* fix text areas to look like labels - * dig down to the appropriate container level - * then set the backgrounds to transparent + /** + * Given a string (normally from the JTextArea which holds the properties for a Property Type) + * this method converts those lines into a List of EditTokenProperty objects. It checks for + * duplicates along the way, ignoring any it finds. (Should produce a list of warnings to indicate + * which ones are duplicates. See the Light/Sight code for examples.) + * + * @param propertyText + * @return */ - JPanel jPanel = (JPanel) super.getComponent("descriptionContainer"); - List jPanels = - Arrays.stream(jPanel.getComponents()).filter(c -> c instanceof JPanel).toList(); + private List parseTokenProperties(String propertyText) + throws IllegalArgumentException { + List propertyList = new ArrayList(); + BufferedReader reader = new BufferedReader(new StringReader(propertyText)); + CaseInsensitiveHashMap caseCheck = new CaseInsensitiveHashMap(); + List errlog = new LinkedList(); + try { + String original, line; + while ((original = reader.readLine()) != null) { + line = original = original.trim(); + if (line.length() == 0) { + continue; + } + + TokenProperty property = new TokenProperty(); + + // Prefix + while (true) { + if (line.startsWith("*")) { + property.setShowOnStatSheet(true); + line = line.substring(1); + continue; + } + if (line.startsWith("@")) { + property.setOwnerOnly(true); + line = line.substring(1); + continue; + } + if (line.startsWith("#")) { + property.setGMOnly(true); + line = line.substring(1); + continue; + } + + // Ran out of special characters + break; + } + + // default value + // had to do this here since the short name is not built + // to take advantage of multiple opening/closing parenthesis + // in a single property line + int indexDefault = line.indexOf(':'); + if (indexDefault > 0) { + String defaultVal = line.substring(indexDefault + 1).trim(); + if (defaultVal.length() > 0) { + property.setDefaultValue(defaultVal); + } + + // remove the default value from the end of the string... + line = line.substring(0, indexDefault); + } + // Suffix + // (Really should handle nested parens here) + int index = line.indexOf('('); + if (index > 0) { + int indexClose = line.lastIndexOf(')'); + // Check for unenclosed parentheses. Fix #1575. + if (indexClose < index) { + MapTool.showError(I18N.getText("CampaignPropertyDialog.error.parenthesis", line)); + throw new IllegalArgumentException("Missing parenthesis"); + } + String shortName = line.substring(index + 1, indexClose).trim(); + if (shortName.length() > 0) { + property.setShortName(shortName); + } + line = line.substring(0, index); + } + line = line.trim(); + property.setName(line); + // Since property names are not case-sensitive, let's make sure that we don't + // already have this name represented somewhere in the list. + String old = caseCheck.get(line); + if (old != null) { + // Perhaps these properties should produce warnings at all, but what it someone + // is actually using them as property names! + if (old.startsWith("---")) + errlog.add( + I18N.getText("msg.error.mtprops.properties.duplicateComment", original, old)); + else errlog.add(I18N.getText("msg.error.mtprops.properties.duplicate", original, old)); + } else { + propertyList.add(property); + caseCheck.put(line, original); + } + } - Color transparent = new Color(0, 0, 0, 1); - for (Component panel : jPanels) { - JPanel jp = (JPanel) panel; - Component[] components = jp.getComponents(); - Arrays.stream(components).toList().forEach(c -> c.setBackground(transparent)); + } catch (IOException ioe) { + // If this happens, I'll check into the nearest insane asylum + MapTool.showError("IOException during parsing of properties?!", ioe); + } + caseCheck.clear(); + if (!errlog.isEmpty()) { + errlog.add(0, I18N.getText("msg.error.mtprops.properties.title", editingType)); + errlog.add(I18N.getText("msg.error.mtprops.properties.ending")); + MapTool.showFeedback(errlog.toArray()); + errlog.clear(); + throw new IllegalArgumentException(); // Don't save the properties... + } + return propertyList; } - JTable propertyTable = getTokenPropertiesTable(); + public void prettify() { + + /* fix text areas to look like labels + * dig down to the appropriate container level + * then set the backgrounds to transparent + */ + JPanel jPanel = (JPanel) super.getComponent("descriptionContainer"); + List jPanels = + Arrays.stream(jPanel.getComponents()).filter(c -> c instanceof JPanel).toList(); + + Color transparent = new Color(0, 0, 0, 1); + for (Component panel : jPanels) { + JPanel jp = (JPanel) panel; + Component[] components = jp.getComponents(); + Arrays.stream(components).toList().forEach(c -> c.setBackground(transparent)); + } + + JTable propertyTable = getTokenPropertiesTable(); /* prettify - take cell background colour and adjust the luminance for cell contrast. change the hue and saturation for the grid line colour */ - Color bg, bgSmall, gridColour; - bg = propertyTable.getTableHeader().getComponent(0).getBackground(); // get background colour - float[] hsbComponents = new float[3]; - Color.RGBtoHSB(bg.getRed(), bg.getGreen(), bg.getBlue(), hsbComponents); // convert to HSB - - boolean lighten = hsbComponents[2] < 0.5f; // to determine direction of change - hsbComponents[2] = - lighten - ? hsbComponents[2] + 0.015f - : hsbComponents[2] - 0.025f; // small change in brilliance - bgSmall = new Color(Color.HSBtoRGB(hsbComponents[0], hsbComponents[1], hsbComponents[2])); - - hsbComponents[2] = - lighten - ? hsbComponents[2] + 0.04f - : hsbComponents[2] - 0.02f; // bigger change in brilliance - bg = new Color(Color.HSBtoRGB(hsbComponents[0], hsbComponents[1], hsbComponents[2])); - - hsbComponents[0] = - hsbComponents[0] < 0.5 - ? hsbComponents[0] + 0.5f - : hsbComponents[0] - 0.5f; // change hue 180 degrees - hsbComponents[1] = - hsbComponents[1] < 0.25 - ? hsbComponents[1] + 0.25f // increase saturation if it is low - : hsbComponents[1]; - gridColour = new Color(Color.HSBtoRGB(hsbComponents[0], hsbComponents[1], hsbComponents[2])); - - DefaultTableCellRenderer cellRenderer = - new DefaultTableCellRenderer(); // cell renderer for contrasting cells - cellRenderer.setBackground(bgSmall); - cellRenderer.setHorizontalAlignment(DefaultTableCellRenderer.LEFT); - - DefaultTableCellRenderer headerRenderer = - new DefaultTableCellRenderer(); // cell renderer for contrasting headings - headerRenderer.setBackground(bg); - headerRenderer.setHorizontalAlignment(DefaultTableCellRenderer.CENTER); - headerRenderer.setVerticalAlignment(SwingConstants.TOP); - - DefaultTableCellRenderer headerRenderer2 = new DefaultTableCellRenderer(); - headerRenderer2.setVerticalAlignment(SwingConstants.TOP); - headerRenderer2.setHorizontalAlignment(SwingConstants.CENTER); - - DefaultTableCellRenderer rowHeaderRenderer = - new DefaultTableCellRenderer(); // cell renderer for contrasting headings - headerRenderer.setBackground(bg); - headerRenderer.setHorizontalAlignment(DefaultTableCellRenderer.LEFT); - - propertyTable.setGridColor(gridColour); - propertyTable.setIntercellSpacing(new Dimension(2, 2)); - propertyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - propertyTable.setShowHorizontalLines(true); - propertyTable.getTableHeader().setResizingAllowed(true); - propertyTable.setFillsViewportHeight(true); - - for (int i = 0; i < propertyTable.getColumnCount(); i++) { - - switch (i) { // set column shading - case 0, 2, 4, 6 -> propertyTable - .getColumnModel() - .getColumn(i) - .setHeaderRenderer(headerRenderer); - case 1, 3 -> { - propertyTable.getColumnModel().getColumn(i).setCellRenderer(cellRenderer); - propertyTable.getColumnModel().getColumn(i).setHeaderRenderer(headerRenderer2); + Color bg, bgSmall, gridColour; + bg = propertyTable.getTableHeader().getComponent(0).getBackground(); // get background colour + float[] hsbComponents = new float[3]; + Color.RGBtoHSB(bg.getRed(), bg.getGreen(), bg.getBlue(), hsbComponents); // convert to HSB + + boolean lighten = hsbComponents[2] < 0.5f; // to determine direction of change + hsbComponents[2] = + lighten + ? hsbComponents[2] + 0.015f + : hsbComponents[2] - 0.025f; // small change in brilliance + bgSmall = new Color(Color.HSBtoRGB(hsbComponents[0], hsbComponents[1], hsbComponents[2])); + + hsbComponents[2] = + lighten + ? hsbComponents[2] + 0.04f + : hsbComponents[2] - 0.02f; // bigger change in brilliance + bg = new Color(Color.HSBtoRGB(hsbComponents[0], hsbComponents[1], hsbComponents[2])); + + hsbComponents[0] = + hsbComponents[0] < 0.5 + ? hsbComponents[0] + 0.5f + : hsbComponents[0] - 0.5f; // change hue 180 degrees + hsbComponents[1] = + hsbComponents[1] < 0.25 + ? hsbComponents[1] + 0.25f // increase saturation if it is low + : hsbComponents[1]; + gridColour = new Color(Color.HSBtoRGB(hsbComponents[0], hsbComponents[1], hsbComponents[2])); + + DefaultTableCellRenderer cellRenderer = + new DefaultTableCellRenderer(); // cell renderer for contrasting cells + cellRenderer.setBackground(bgSmall); + cellRenderer.setHorizontalAlignment(DefaultTableCellRenderer.LEFT); + + DefaultTableCellRenderer headerRenderer = + new DefaultTableCellRenderer(); // cell renderer for contrasting headings + headerRenderer.setBackground(bg); + headerRenderer.setHorizontalAlignment(DefaultTableCellRenderer.CENTER); + headerRenderer.setVerticalAlignment(SwingConstants.TOP); + + DefaultTableCellRenderer headerRenderer2 = new DefaultTableCellRenderer(); + headerRenderer2.setVerticalAlignment(SwingConstants.TOP); + headerRenderer2.setHorizontalAlignment(SwingConstants.CENTER); + + DefaultTableCellRenderer rowHeaderRenderer = + new DefaultTableCellRenderer(); // cell renderer for contrasting headings + headerRenderer.setBackground(bg); + headerRenderer.setHorizontalAlignment(DefaultTableCellRenderer.LEFT); + + propertyTable.setGridColor(gridColour); + propertyTable.setIntercellSpacing(new Dimension(2, 2)); + propertyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + propertyTable.setShowHorizontalLines(true); + propertyTable.getTableHeader().setResizingAllowed(true); + propertyTable.setFillsViewportHeight(true); + + for (int i = 0; i < propertyTable.getColumnCount(); i++) { + + switch (i) { // set column shading + case 0, 2, 4, 6 -> propertyTable + .getColumnModel() + .getColumn(i) + .setHeaderRenderer(headerRenderer); + case 1, 3 -> { + propertyTable.getColumnModel().getColumn(i).setCellRenderer(cellRenderer); + propertyTable.getColumnModel().getColumn(i).setHeaderRenderer(headerRenderer2); + } + } + + switch (i) { // set column sizes + case 0, 2, 3 -> { + propertyTable.getColumnModel().getColumn(i).setMinWidth(60); + propertyTable.getColumnModel().getColumn(i).setPreferredWidth(80); + } + case 1 -> { + propertyTable.getColumnModel().getColumn(i).setMinWidth(45); + propertyTable.getColumnModel().getColumn(i).setMaxWidth(100); + propertyTable.getColumnModel().getColumn(i).setPreferredWidth(55); + } + case 4, 5, 6 -> { + propertyTable.getColumnModel().getColumn(i).setMinWidth(50); + propertyTable.getColumnModel().getColumn(i).setMaxWidth(80); + propertyTable.getColumnModel().getColumn(i).setPreferredWidth(50); + } + } } - } + Font hFont = propertyTable.getTableHeader().getComponent(0).getFont(); + Dimension headerDim = propertyTable.getTableHeader().getSize(); + headerDim.height = (int) (hFont.getSize() * 3.41); + propertyTable.getTableHeader().setPreferredSize(headerDim); + } - switch (i) { // set column sizes - case 0, 2, 3 -> { - propertyTable.getColumnModel().getColumn(i).setMinWidth(60); - propertyTable.getColumnModel().getColumn(i).setPreferredWidth(80); + private class TypeListModel extends AbstractListModel { + public Object getElementAt(int index) { + List names = new ArrayList(tokenTypeMap.keySet()); + Collections.sort(names); + return names.get(index); } - case 1 -> { - propertyTable.getColumnModel().getColumn(i).setMinWidth(45); - propertyTable.getColumnModel().getColumn(i).setMaxWidth(100); - propertyTable.getColumnModel().getColumn(i).setPreferredWidth(55); + + public int getSize() { + return tokenTypeMap.size(); } - case 4, 5, 6 -> { - propertyTable.getColumnModel().getColumn(i).setMinWidth(50); - propertyTable.getColumnModel().getColumn(i).setMaxWidth(80); - propertyTable.getColumnModel().getColumn(i).setPreferredWidth(50); + } + + /** + * Gets the Token Property Type rename operations that have occurred. + * + * @return a {@link Map} of renames. + */ + public SortedMap getRenameTypes() { + return renameTypes; + } + + /** A List cell renderer that calls out default property type. */ + private class TokenTypeCellRenderer extends JLabel implements ListCellRenderer { + + public TokenTypeCellRenderer() { + setOpaque(true); + } + + @Override + public Component getListCellRendererComponent( + JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + + var val = value.toString(); + if (val.equals(defaultPropertyType)) { + setText( + "" + + val + + "     (" + + I18N.getString("TokenPropertiesPanel.defaultPropertyType") + + ")"); + } else { + setText("" + val + ""); + } + + if (isSelected) { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } else { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + + return this; } - } - } - Font hFont = propertyTable.getTableHeader().getComponent(0).getFont(); - Dimension headerDim = propertyTable.getTableHeader().getSize(); - headerDim.height = (int) (hFont.getSize() * 3.41); - propertyTable.getTableHeader().setPreferredSize(headerDim); - } - - private class TypeListModel extends AbstractListModel { - public Object getElementAt(int index) { - List names = new ArrayList(tokenTypeMap.keySet()); - Collections.sort(names); - return names.get(index); - } - - public int getSize() { - return tokenTypeMap.size(); - } - } - - /** - * Gets the Token Property Type rename operations that have occurred. - * - * @return a {@link Map} of renames. - */ - public SortedMap getRenameTypes() { - return renameTypes; - } - - /** A List cell renderer that calls out default property type. */ - private class TokenTypeCellRenderer extends JLabel implements ListCellRenderer { - - public TokenTypeCellRenderer() { - setOpaque(true); - } - - @Override - public Component getListCellRendererComponent( - JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - - var val = value.toString(); - if (val.equals(defaultPropertyType)) { - setText( - "" - + val - + "     (" - + I18N.getString("TokenPropertiesPanel.defaultPropertyType") - + ")"); - } else { - setText("" + val + ""); - } - - if (isSelected) { - setBackground(list.getSelectionBackground()); - setForeground(list.getSelectionForeground()); - } else { - setBackground(list.getBackground()); - setForeground(list.getForeground()); - } - - return this; - } - } + } } diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesTableModel.java index 159083f65d..979ecd1ee4 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesTableModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesTableModel.java @@ -118,7 +118,7 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { var tokenProperty = properties.get(rowIndex); return switch (columnIndex) { case 5, 6 -> tokenProperty - .isShowOnStatSheet(); // GM, Owner only editable if show on stat sheet is set + .isShowOnStatSheet(); // GM, Owner only editable if show on stat sheet is set default -> true; }; } @@ -142,7 +142,7 @@ public void setValueAt(Object aValue, int rowIndex, int columnIndex) { } /** Adds a new token property, with a generated name. */ - public void addProperty() { + public void addProperty(int selectedRow) { var properties = tokenTypeMap.get(tokenType); // First find a unique name, there are so few entries we don't have to worry @@ -160,7 +160,12 @@ public void addProperty() { if (free) { var prop = new TokenProperty(newName); - properties.add(prop); + // append if there is no selection, otherwise insert at selectedRow + if (selectedRow == -1) { + properties.add(prop); + } else { + properties.add(selectedRow, prop); + } break; } seq++; From 5390a99d450f527ce0847b70a0ccc48d72875810 Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Wed, 2 Oct 2024 19:32:16 +0800 Subject: [PATCH 040/146] Added ability to export campaign properties to JSON text file --- .../maptool/client/ui/MapToolFrame.java | 5 +++++ .../CampaignPropertiesDialog.java | 18 +++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java b/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java index 2680fca2e0..c6f91eeaa7 100644 --- a/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java +++ b/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java @@ -36,6 +36,7 @@ import javax.swing.Timer; import javax.swing.border.BevelBorder; import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import javax.xml.parsers.ParserConfigurationException; @@ -884,6 +885,10 @@ public JFileChooser getSavePropsFileChooser() { savePropsFileChooser = new JFileChooser(); savePropsFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); savePropsFileChooser.addChoosableFileFilter(propertiesFilter); + savePropsFileChooser.setFileFilter(propertiesFilter); + savePropsFileChooser.setFileFilter(new FileNameExtensionFilter("JSON", "json")); + savePropsFileChooser.setFileFilter(new FileNameExtensionFilter("Text", "txt")); + savePropsFileChooser.setFileFilter(savePropsFileChooser.getChoosableFileFilters()[0]); savePropsFileChooser.setDialogTitle(I18N.getText("msg.title.exportProperties")); } savePropsFileChooser.setAcceptAllFileFilterUsed(true); diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java index 2038204e7c..4ad9341feb 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java @@ -17,11 +17,13 @@ import static org.apache.commons.text.WordUtils.capitalize; import static org.apache.commons.text.WordUtils.uncapitalize; +import com.google.protobuf.util.JsonFormat; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.Collections; @@ -47,6 +49,7 @@ import net.rptools.maptool.model.LightSource; import net.rptools.maptool.model.ShapeType; import net.rptools.maptool.model.SightType; +import net.rptools.maptool.server.proto.CampaignPropertiesDto; import net.rptools.maptool.util.LightSyntax; import net.rptools.maptool.util.PersistenceUtil; import net.rptools.maptool.util.SightSyntax; @@ -171,7 +174,7 @@ private void initAddRepoButton() { button.addActionListener( e -> { String newRepo = getNewServerTextField().getText(); - if (newRepo == null || newRepo.length() == 0) { + if (newRepo == null || newRepo.isEmpty()) { return; } // TODO: Check for uniqueness @@ -415,6 +418,7 @@ private void initExportButton() { // END HACK JFileChooser chooser = MapTool.getFrame().getSavePropsFileChooser(); + boolean tryAgain = true; while (tryAgain) { if (chooser.showSaveDialog(MapTool.getFrame()) != JFileChooser.APPROVE_OPTION) { @@ -440,8 +444,16 @@ private void initExportButton() { } } try { - PersistenceUtil.saveCampaignProperties(campaign, chooser.getSelectedFile()); - MapTool.showInformation("Properties Saved."); + if (selectedFile.getName().endsWith(".mtprops")) { + PersistenceUtil.saveCampaignProperties(campaign, chooser.getSelectedFile()); + MapTool.showInformation("Properties Saved."); + } else { + CampaignPropertiesDto campaignPropertiesDto = + MapTool.getCampaign().getCampaignProperties().toDto(); + FileOutputStream fos = new FileOutputStream(chooser.getSelectedFile()); + fos.write(JsonFormat.printer().print(campaignPropertiesDto).getBytes()); + } + } catch (IOException ioe) { MapTool.showError("Could not save properties: ", ioe); } From 524b106e25a32fadf30281e8f312c29cae22ca6b Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Wed, 2 Oct 2024 19:47:56 +0800 Subject: [PATCH 041/146] Added message advising JSON exports cannot be imported. --- .../ui/campaignproperties/CampaignPropertiesDialog.java | 5 +++++ .../resources/net/rptools/maptool/language/i18n.properties | 1 + 2 files changed, 6 insertions(+) diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java index 4ad9341feb..cc1a5d4ec6 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java @@ -448,6 +448,11 @@ private void initExportButton() { PersistenceUtil.saveCampaignProperties(campaign, chooser.getSelectedFile()); MapTool.showInformation("Properties Saved."); } else { + MapTool.showMessage( + "CampaignPropertiesDialog.export.message", + "msg.title.exportProperties", + JOptionPane.INFORMATION_MESSAGE, + null); CampaignPropertiesDto campaignPropertiesDto = MapTool.getCampaign().getCampaignProperties().toDto(); FileOutputStream fos = new FileOutputStream(chooser.getSelectedFile()); diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 2a52ce3242..e28a6e524e 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -812,6 +812,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties from a file CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import Predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From 5324e6513b6dcbd973f89a75ae3bca14a05794ec Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Wed, 2 Oct 2024 19:50:42 +0800 Subject: [PATCH 042/146] Spotwhinge --- .../TokenPropertiesManagementPanel.java | 1585 +++++++++-------- .../TokenPropertiesTableModel.java | 2 +- 2 files changed, 794 insertions(+), 793 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanel.java b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanel.java index 659cb0ced3..c3af732ba0 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanel.java @@ -40,821 +40,822 @@ import org.apache.logging.log4j.Logger; public class TokenPropertiesManagementPanel extends AbeillePanel { - private static final Logger log = LogManager.getLogger(TokenPropertiesManagementPanel.class); - private Map> tokenTypeMap; - private final Map tokenTypeStatSheetMap = new HashMap<>(); - private String editingType; - - private final SortedMap renameTypes = new TreeMap<>(); - - private String defaultPropertyType; - - CampaignProperties campaignProperties; - - public TokenPropertiesManagementPanel() { - super(new TokenPropertiesManagementPanelView().getRootComponent()); - - panelInit(); - } - - public void copyCampaignToUI(CampaignProperties cp) { - campaignProperties = cp; - defaultPropertyType = cp.getDefaultTokenPropertyType(); - - tokenTypeMap = new HashMap<>(); - campaignProperties - .getTokenTypeMap() - .forEach( - (k, v) -> - tokenTypeMap.put(k, new ArrayList<>(v.stream().map(TokenProperty::new).toList()))); - var ssManager = new StatSheetManager(); - tokenTypeMap - .keySet() - .forEach( - tt -> - tokenTypeStatSheetMap.put(tt, campaignProperties.getTokenTypeDefaultStatSheet(tt))); - updateTypeList(); - } - - public void copyUIToCampaign(Campaign campaign) { - - campaign.getTokenTypeMap().clear(); - campaign.getTokenTypeMap().putAll(tokenTypeMap); - campaign - .getTokenTypeMap() - .keySet() - .forEach(tt -> campaign.setTokenTypeDefaultSheetId(tt, tokenTypeStatSheetMap.get(tt))); - campaign.setDefaultTokenPropertyType(defaultPropertyType); - } - - public void finalizeCellEditing() { - if (getTokenPropertiesTable().isEditing()) { - getTokenPropertiesTable().getCellEditor().stopCellEditing(); + private static final Logger log = LogManager.getLogger(TokenPropertiesManagementPanel.class); + private Map> tokenTypeMap; + private final Map tokenTypeStatSheetMap = new HashMap<>(); + private String editingType; + + private final SortedMap renameTypes = new TreeMap<>(); + + private String defaultPropertyType; + + CampaignProperties campaignProperties; + + public TokenPropertiesManagementPanel() { + super(new TokenPropertiesManagementPanelView().getRootComponent()); + + panelInit(); + } + + public void copyCampaignToUI(CampaignProperties cp) { + campaignProperties = cp; + defaultPropertyType = cp.getDefaultTokenPropertyType(); + + tokenTypeMap = new HashMap<>(); + campaignProperties + .getTokenTypeMap() + .forEach( + (k, v) -> + tokenTypeMap.put(k, new ArrayList<>(v.stream().map(TokenProperty::new).toList()))); + var ssManager = new StatSheetManager(); + tokenTypeMap + .keySet() + .forEach( + tt -> + tokenTypeStatSheetMap.put(tt, campaignProperties.getTokenTypeDefaultStatSheet(tt))); + updateTypeList(); + } + + public void copyUIToCampaign(Campaign campaign) { + + campaign.getTokenTypeMap().clear(); + campaign.getTokenTypeMap().putAll(tokenTypeMap); + campaign + .getTokenTypeMap() + .keySet() + .forEach(tt -> campaign.setTokenTypeDefaultSheetId(tt, tokenTypeStatSheetMap.get(tt))); + campaign.setDefaultTokenPropertyType(defaultPropertyType); + } + + public void finalizeCellEditing() { + if (getTokenPropertiesTable().isEditing()) { + getTokenPropertiesTable().getCellEditor().stopCellEditing(); + } + } + + public JList getTokenTypeList() { + JList list = (JList) getComponent("tokenTypeList"); + if (list == null) { + list = new JList(); + } + return list; + } + + public JTextField getTokenTypeName() { + return (JTextField) getComponent("tokenTypeName"); + } + + public JButton getTypeAddButton() { + return (JButton) getComponent("typeAddButton"); + } + + public JButton getTypeDeleteButton() { + return (JButton) getComponent("typeDeleteButton"); + } + + public JButton getTypeDuplicateButton() { + return (JButton) getComponent("typeDuplicateButton"); + } + + public JButton getPropertyMoveUpButton() { + return (JButton) getComponent("propertyMoveUpButton"); + } + + public JButton getPropertyMoveDownButton() { + return (JButton) getComponent("propertyMoveDownButton"); + } + + public JButton getPropertyAddButton() { + return (JButton) getComponent("propertyAddButton"); + } + + public JButton getPropertyDeleteButton() { + return (JButton) getComponent("propertyDeleteButton"); + } + + public JTable getTokenPropertiesTable() { + return (JTable) getComponent("propertiesTable"); + } + + public JComboBox getStatSheetLocationComboBox() { + return (JComboBox) getComponent("statSheetLocationComboBox"); + } + + public JComboBox getStatSheetComboBox() { + return (JComboBox) getComponent("statSheetComboBox"); + } + + public JButton getTypeSetAsDefault() { + return (JButton) getComponent("typeDefaultButton"); + } + + public JButton getHelpButton() { + return (JButton) getComponent("helpButton"); + } + + public JPanel getDescriptionContainer() { + return (JPanel) getComponent("descriptionContainer"); + } + + public TokenPropertiesTableModel getTokenPropertiesTableModel() { + return (TokenPropertiesTableModel) getTokenPropertiesTable().getModel(); + } + + public JScrollPane getTableScrollPane() { + return (JScrollPane) getComponent("tokenPropertiesTableScrollPane"); + } + + public void initTypeAddButton() { + getTypeAddButton() + .addActionListener( + e -> + EventQueue.invokeLater( + () -> { + // First find a unique name, there are so few entries we don't have to worry + // about being fancy + int seq = 1; + String name = + I18N.getText("campaignPropertiesDialog.newTokenTypeDefaultName", seq); + while (tokenTypeMap.containsKey(name)) { + seq++; + name = + I18N.getText("campaignPropertiesDialog.newTokenTypeDefaultName", seq); + } + + var newName = + (String) + JOptionPane.showInputDialog( + this, + I18N.getText("campaignPropertiesDialog.newTokenTypeName"), + I18N.getText("campaignPropertiesDialog.newTokenTypeTitle"), + JOptionPane.PLAIN_MESSAGE, + null, + null, + name); + if (newName != null) { + tokenTypeMap.put(newName, new LinkedList<>()); + updateTypeList(); + getTokenTypeList().setSelectedValue(newName, true); + } + })); + } + + public void initTypeDeleteButton() { + var button = getTypeDeleteButton(); + button.addActionListener( + e -> { + var type = (String) getTokenTypeList().getSelectedValue(); + if (type != null) { + JPanel renameToPanel = new JPanel(); + JComboBox types = + new JComboBox<>( + tokenTypeMap.keySet().stream() + .filter(t -> !t.equals(type)) + .map(String::toString) + .toArray(String[]::new)); + renameToPanel.add( + new JLabel(I18N.getText("campaignPropertiesDialog.tokenTypeNameDeleteMessage"))); + renameToPanel.add(types); + int option = + JOptionPane.showConfirmDialog( + this, + renameToPanel, + I18N.getText("campaignPropertiesDialog.tokenTypeNameDeleteTitle", type), + JOptionPane.OK_CANCEL_OPTION); + if (option == JOptionPane.OK_OPTION) { + var newType = (String) types.getSelectedItem(); + if (newType != null) { + renameTypes.put(type, newType); + tokenTypeMap.remove(type); + updateTypeList(); + } + } + } + }); + button.setEnabled(false); + } + + public void initTypeDefaultButton() { + var button = getTypeSetAsDefault(); + button.addActionListener( + l -> { + var propertyType = (String) getTokenTypeList().getSelectedValue(); + if (propertyType != null) { + defaultPropertyType = propertyType; + button.setEnabled(false); + var delButton = getTypeDeleteButton(); + delButton.setEnabled(false); + } + }); + + button.setEnabled(false); + } + + public void initPropertyMoveUpButton() { + var button = getPropertyMoveUpButton(); + button.addActionListener( + l -> { + finalizeCellEditing(); + JTable propertiesTable = getTokenPropertiesTable(); + var selectedRow = propertiesTable.getSelectedRow(); + if (selectedRow <= 0) { + return; + } + + var model = getTokenPropertiesTableModel(); + model.movePropertyUp(selectedRow); + --selectedRow; + propertiesTable.setRowSelectionInterval(selectedRow, selectedRow); + propertiesTable.scrollRectToVisible(propertiesTable.getCellRect(selectedRow, 0, true)); + }); + button.setEnabled(false); + } + + public void initPropertyMoveDownButton() { + var button = getPropertyMoveDownButton(); + button.addActionListener( + l -> { + finalizeCellEditing(); + JTable propertiesTable = getTokenPropertiesTable(); + var selectedRow = propertiesTable.getSelectedRow(); + if (selectedRow < 0 || selectedRow >= propertiesTable.getRowCount() - 1) { + return; + } + + var model = getTokenPropertiesTableModel(); + model.movePropertyDown(selectedRow); + ++selectedRow; + propertiesTable.setRowSelectionInterval(selectedRow, selectedRow); + propertiesTable.scrollRectToVisible(propertiesTable.getCellRect(selectedRow, 0, true)); + }); + button.setEnabled(false); + } + + public void initPropertyAddButton() { + var button = getPropertyAddButton(); + button.addActionListener( + e -> + EventQueue.invokeLater( + () -> { + finalizeCellEditing(); + JTable propertiesTable = getTokenPropertiesTable(); + var model = getTokenPropertiesTableModel(); + // selected row is -1 for no selection causing property to be appended to list + // instead of inserted + int selectedRow = propertiesTable.getSelectedRow(); + model.addProperty(selectedRow); + int count = model.getRowCount(); + propertiesTable.scrollRectToVisible( + propertiesTable.getCellRect( + selectedRow == -1 ? count - 1 : selectedRow, 0, true)); + propertiesTable.repaint(); + })); + button.setEnabled(false); + } + + public void initPropertyDeleteButton() { + var button = getPropertyDeleteButton(); + button.addActionListener( + e -> + EventQueue.invokeLater( + () -> { + finalizeCellEditing(); + var model = getTokenPropertiesTableModel(); + model.deleteProperty(getTokenPropertiesTable().getSelectedRow()); + })); + button.setEnabled(false); + } + + public void initTypeDuplicateButton() { + var button = getTypeDuplicateButton(); + button.addActionListener( + e -> + EventQueue.invokeLater( + () -> { + log.info("Type Duplicate - button action"); + var propertyType = (String) getTokenTypeList().getSelectedValue(); + if (propertyType != null) { + String newName = propertyType + "@"; + tokenTypeMap.put(newName, tokenTypeMap.get(propertyType)); + updateTypeList(); + getTokenTypeList().setSelectedValue(newName, true); + button.setEnabled(true); + } + })); + button.setEnabled(false); + } + + public void initDescriptionContainer() { + getDescriptionContainer().setVisible(false); + } + + public void initHelpButton() { + var button = getHelpButton(); + button.addActionListener( + e -> + EventQueue.invokeLater( + () -> { + JPanel helpText = getDescriptionContainer(); + helpText.setVisible(!helpText.isVisible()); + })); + button.setEnabled(true); + } + + public void initPropertyTable() { + var propertyTable = getTokenPropertiesTable(); + propertyTable.setModel(new TokenPropertiesTableModel()); + propertyTable.setDefaultEditor( + LargeEditableText.class, new TextFieldEditorButtonTableCellEditor()); + propertyTable + .getSelectionModel() + .addListSelectionListener( + e -> { + if (e.getValueIsAdjusting()) { + return; + } + + var deleteButton = getPropertyDeleteButton(); + deleteButton.setEnabled(getTokenPropertiesTable().getSelectedRow() >= 0); + + var moveUpButton = getPropertyMoveUpButton(); + moveUpButton.setEnabled(getTokenPropertiesTable().getSelectedRow() > 0); + + var moveDownButton = getPropertyMoveDownButton(); + // Note: this works even if selection is empty (getSelectedRow() == -1). + moveDownButton.setEnabled( + getTokenPropertiesTable().getSelectedRow() + < getTokenPropertiesTable().getRowCount() - 1); + }); + } + + public void initTokenTypeName() { + var field = getTokenTypeName(); + field.setEditable(false); + field.addActionListener( + event -> { + int option = + JOptionPane.showConfirmDialog( + this, + I18N.getText("campaignPropertiesDialog.tokenTypeNameChangeWarning"), + I18N.getText("campaignPropertiesDialog.tokenTypeNameChangeTitle"), + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE); + if (option == JOptionPane.OK_OPTION) { + var ttList = getTokenTypeList(); + var oldName = (String) ttList.getSelectedValue(); + var newName = field.getText(); + tokenTypeMap.put(newName, tokenTypeMap.remove(oldName)); + tokenTypeStatSheetMap.put(newName, tokenTypeStatSheetMap.remove(oldName)); + ttList.setSelectedValue(newName, true); + updateExistingTokenTypes(oldName, newName); + } + }); + } + + private void updateExistingTokenTypes(String oldName, String newName) { + if (oldName == null || newName == null || oldName.equals(newName)) { + return; + } + if (defaultPropertyType.equals(oldName)) { + defaultPropertyType = newName; + } + renameTypes.put(oldName, newName); + } + + public void initTypeList() { + + getTokenTypeList() + .addListSelectionListener( + e -> { + if (e.getValueIsAdjusting()) { + return; + } + + var propertyType = + getTokenTypeList().getSelectedValue() == null + ? null + : getTokenTypeList().getSelectedValue().toString(); + + finalizeCellEditing(); + + if (propertyType == null) { + reset(); + getPropertyAddButton().setEnabled(false); + getTypeDeleteButton().setEnabled(false); + getTypeDuplicateButton().setEnabled(false); + getTokenTypeName().setEditable(false); + getStatSheetComboBox().setEnabled(false); + getStatSheetLocationComboBox().setEnabled(false); + getTypeSetAsDefault().setEnabled(false); + } else { + bind((String) getTokenTypeList().getSelectedValue()); + getPropertyAddButton().setEnabled(true); + getTypeDuplicateButton().setEnabled(true); + getTokenTypeName().setEditable(true); + // Can't delete the default property + if (propertyType.equals(defaultPropertyType)) { + getTypeDeleteButton().setEnabled(false); + } else { + getTypeDeleteButton().setEnabled(true); + } + getStatSheetComboBox().setEnabled(true); + getStatSheetLocationComboBox().setEnabled(true); + populateStatSheetComboBoxes(propertyType); + if (!propertyType.equals(defaultPropertyType)) { + getTypeSetAsDefault().setEnabled(true); + } else { + getTypeSetAsDefault().setEnabled(false); + } + } + }); + getTokenTypeList().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + getTokenTypeList().setCellRenderer(new TokenTypeCellRenderer()); + } + + private void populateStatSheetComboBoxes(String propertyType) { + var combo = getStatSheetComboBox(); + combo.removeAllItems(); + var ssManager = new StatSheetManager(); + ssManager.getStatSheets(propertyType).stream() + .sorted(Comparator.comparing(StatSheet::description)) + .forEach(ss -> combo.addItem(ss)); + var statSheetProperty = tokenTypeStatSheetMap.get(propertyType); + String id; + if (statSheetProperty == null) { + id = ssManager.getDefaultStatSheetId(); + tokenTypeStatSheetMap.put( + propertyType, + new StatSheetProperties( + ssManager.getDefaultStatSheetId(), StatSheetLocation.BOTTOM_LEFT)); + } else { + id = statSheetProperty.id(); + } + combo.setSelectedItem(ssManager.getStatSheet(id)); + + var locationCombo = getStatSheetLocationComboBox(); + locationCombo.setSelectedItem(tokenTypeStatSheetMap.get(propertyType).location()); + } + + public void initStatSheetDetails() { + var locationCombo = getStatSheetLocationComboBox(); + locationCombo.setEnabled(false); + Arrays.stream(StatSheetLocation.values()).forEach(locationCombo::addItem); + locationCombo.addActionListener( + l -> { + if (getStatSheetLocationComboBox().hasFocus()) { // only if user has made change + var location = (StatSheetLocation) locationCombo.getSelectedItem(); + var tokenType = (String) getTokenTypeList().getSelectedValue(); + if (location != null && tokenType != null) { + var id = tokenTypeStatSheetMap.get(tokenType).id(); + tokenTypeStatSheetMap.put(tokenType, new StatSheetProperties(id, location)); + } + } + }); + + var combo = getStatSheetComboBox(); + combo.setEnabled(false); + combo.setRenderer(new StatSheetComboBoxRenderer()); + combo.addActionListener( + l -> { + if (getStatSheetComboBox().hasFocus()) { // Only if user has made change + var ss = (StatSheet) combo.getSelectedItem(); + var tokenType = (String) getTokenTypeList().getSelectedValue(); + if (ss != null && tokenType != null) { + var id = new StatSheetManager().getId(ss); + var location = tokenTypeStatSheetMap.get(tokenType).location(); + tokenTypeStatSheetMap.put(tokenType, new StatSheetProperties(id, location)); + getStatSheetLocationComboBox().setSelectedItem(location); + } + } + }); + } + + private void bind(String type) { + + editingType = type; + + getTokenTypeName().setText(type != null ? type : ""); + var model = getTokenPropertiesTableModel(); + model.setPropertyType(type); + } + + private void reset() { + + bind((String) null); + } + + private void updateTypeList() { + getTokenTypeList().setModel(new TypeListModel()); + getTokenPropertiesTableModel().setPropertyTypeMap(tokenTypeMap); + } + + private String compileTokenProperties(List propertyList) { + + // Sanity + if (propertyList == null) { + return ""; + } + + StringBuilder builder = new StringBuilder(); + + for (TokenProperty property : propertyList) { + if (property.isShowOnStatSheet()) { + builder.append("*"); + } + if (property.isOwnerOnly()) { + builder.append("@"); + } + if (property.isGMOnly()) { + builder.append("#"); + } + builder.append(property.getName()); + if (property.getShortName() != null) { + builder.append(" (").append(property.getShortName()).append(")"); + } + if (property.getDefaultValue() != null) { + builder.append(":").append(property.getDefaultValue()); + } + builder.append("\n"); + } + + return builder.toString(); + } + + /** + * Given a string (normally from the JTextArea which holds the properties for a Property Type) + * this method converts those lines into a List of EditTokenProperty objects. It checks for + * duplicates along the way, ignoring any it finds. (Should produce a list of warnings to indicate + * which ones are duplicates. See the Light/Sight code for examples.) + * + * @param propertyText + * @return + */ + private List parseTokenProperties(String propertyText) + throws IllegalArgumentException { + List propertyList = new ArrayList(); + BufferedReader reader = new BufferedReader(new StringReader(propertyText)); + CaseInsensitiveHashMap caseCheck = new CaseInsensitiveHashMap(); + List errlog = new LinkedList(); + try { + String original, line; + while ((original = reader.readLine()) != null) { + line = original = original.trim(); + if (line.length() == 0) { + continue; } - } - public JList getTokenTypeList() { - JList list = (JList) getComponent("tokenTypeList"); - if (list == null) { - list = new JList(); + TokenProperty property = new TokenProperty(); + + // Prefix + while (true) { + if (line.startsWith("*")) { + property.setShowOnStatSheet(true); + line = line.substring(1); + continue; + } + if (line.startsWith("@")) { + property.setOwnerOnly(true); + line = line.substring(1); + continue; + } + if (line.startsWith("#")) { + property.setGMOnly(true); + line = line.substring(1); + continue; + } + + // Ran out of special characters + break; } - return list; - } - - public JTextField getTokenTypeName() { - return (JTextField) getComponent("tokenTypeName"); - } - - public JButton getTypeAddButton() { - return (JButton) getComponent("typeAddButton"); - } - - public JButton getTypeDeleteButton() { - return (JButton) getComponent("typeDeleteButton"); - } - - public JButton getTypeDuplicateButton() { - return (JButton) getComponent("typeDuplicateButton"); - } - - public JButton getPropertyMoveUpButton() { - return (JButton) getComponent("propertyMoveUpButton"); - } - - public JButton getPropertyMoveDownButton() { - return (JButton) getComponent("propertyMoveDownButton"); - } - - public JButton getPropertyAddButton() { - return (JButton) getComponent("propertyAddButton"); - } - - public JButton getPropertyDeleteButton() { - return (JButton) getComponent("propertyDeleteButton"); - } - - public JTable getTokenPropertiesTable() { - return (JTable) getComponent("propertiesTable"); - } - - public JComboBox getStatSheetLocationComboBox() { - return (JComboBox) getComponent("statSheetLocationComboBox"); - } - - public JComboBox getStatSheetComboBox() { - return (JComboBox) getComponent("statSheetComboBox"); - } - - public JButton getTypeSetAsDefault() { - return (JButton) getComponent("typeDefaultButton"); - } - - public JButton getHelpButton() { - return (JButton) getComponent("helpButton"); - } - - public JPanel getDescriptionContainer() { - return (JPanel) getComponent("descriptionContainer"); - } - - public TokenPropertiesTableModel getTokenPropertiesTableModel() { - return (TokenPropertiesTableModel) getTokenPropertiesTable().getModel(); - } - - public JScrollPane getTableScrollPane() { - return (JScrollPane) getComponent("tokenPropertiesTableScrollPane"); - } - - public void initTypeAddButton() { - getTypeAddButton() - .addActionListener( - e -> - EventQueue.invokeLater( - () -> { - // First find a unique name, there are so few entries we don't have to worry - // about being fancy - int seq = 1; - String name = - I18N.getText("campaignPropertiesDialog.newTokenTypeDefaultName", seq); - while (tokenTypeMap.containsKey(name)) { - seq++; - name = - I18N.getText("campaignPropertiesDialog.newTokenTypeDefaultName", seq); - } - - var newName = - (String) - JOptionPane.showInputDialog( - this, - I18N.getText("campaignPropertiesDialog.newTokenTypeName"), - I18N.getText("campaignPropertiesDialog.newTokenTypeTitle"), - JOptionPane.PLAIN_MESSAGE, - null, - null, - name); - if (newName != null) { - tokenTypeMap.put(newName, new LinkedList<>()); - updateTypeList(); - getTokenTypeList().setSelectedValue(newName, true); - } - })); - } - - public void initTypeDeleteButton() { - var button = getTypeDeleteButton(); - button.addActionListener( - e -> { - var type = (String) getTokenTypeList().getSelectedValue(); - if (type != null) { - JPanel renameToPanel = new JPanel(); - JComboBox types = - new JComboBox<>( - tokenTypeMap.keySet().stream() - .filter(t -> !t.equals(type)) - .map(String::toString) - .toArray(String[]::new)); - renameToPanel.add( - new JLabel(I18N.getText("campaignPropertiesDialog.tokenTypeNameDeleteMessage"))); - renameToPanel.add(types); - int option = - JOptionPane.showConfirmDialog( - this, - renameToPanel, - I18N.getText("campaignPropertiesDialog.tokenTypeNameDeleteTitle", type), - JOptionPane.OK_CANCEL_OPTION); - if (option == JOptionPane.OK_OPTION) { - var newType = (String) types.getSelectedItem(); - if (newType != null) { - renameTypes.put(type, newType); - tokenTypeMap.remove(type); - updateTypeList(); - } - } - } - }); - button.setEnabled(false); - } - - public void initTypeDefaultButton() { - var button = getTypeSetAsDefault(); - button.addActionListener( - l -> { - var propertyType = (String) getTokenTypeList().getSelectedValue(); - if (propertyType != null) { - defaultPropertyType = propertyType; - button.setEnabled(false); - var delButton = getTypeDeleteButton(); - delButton.setEnabled(false); - } - }); - - button.setEnabled(false); - } - - public void initPropertyMoveUpButton() { - var button = getPropertyMoveUpButton(); - button.addActionListener( - l -> { - finalizeCellEditing(); - JTable propertiesTable = getTokenPropertiesTable(); - var selectedRow = propertiesTable.getSelectedRow(); - if (selectedRow <= 0) { - return; - } - - var model = getTokenPropertiesTableModel(); - model.movePropertyUp(selectedRow); - --selectedRow; - propertiesTable.setRowSelectionInterval(selectedRow, selectedRow); - propertiesTable.scrollRectToVisible(propertiesTable.getCellRect(selectedRow, 0, true)); - }); - button.setEnabled(false); - } - - public void initPropertyMoveDownButton() { - var button = getPropertyMoveDownButton(); - button.addActionListener( - l -> { - finalizeCellEditing(); - JTable propertiesTable = getTokenPropertiesTable(); - var selectedRow = propertiesTable.getSelectedRow(); - if (selectedRow < 0 || selectedRow >= propertiesTable.getRowCount() - 1) { - return; - } - - var model = getTokenPropertiesTableModel(); - model.movePropertyDown(selectedRow); - ++selectedRow; - propertiesTable.setRowSelectionInterval(selectedRow, selectedRow); - propertiesTable.scrollRectToVisible(propertiesTable.getCellRect(selectedRow, 0, true)); - }); - button.setEnabled(false); - } - - public void initPropertyAddButton() { - var button = getPropertyAddButton(); - button.addActionListener( - e -> - EventQueue.invokeLater( - () -> { - finalizeCellEditing(); - JTable propertiesTable = getTokenPropertiesTable(); - var model = getTokenPropertiesTableModel(); - // selected row is -1 for no selection causing property to be appended to list instead of inserted - int selectedRow = propertiesTable.getSelectedRow(); - model.addProperty(selectedRow); - int count = model.getRowCount(); - propertiesTable.scrollRectToVisible( - propertiesTable.getCellRect( - selectedRow == -1 ? count - 1 : selectedRow, 0, true)); - propertiesTable.repaint(); - })); - button.setEnabled(false); - } - - public void initPropertyDeleteButton() { - var button = getPropertyDeleteButton(); - button.addActionListener( - e -> - EventQueue.invokeLater( - () -> { - finalizeCellEditing(); - var model = getTokenPropertiesTableModel(); - model.deleteProperty(getTokenPropertiesTable().getSelectedRow()); - })); - button.setEnabled(false); - } - - public void initTypeDuplicateButton() { - var button = getTypeDuplicateButton(); - button.addActionListener( - e -> - EventQueue.invokeLater( - () -> { - log.info("Type Duplicate - button action"); - var propertyType = (String) getTokenTypeList().getSelectedValue(); - if (propertyType != null) { - String newName = propertyType + "@"; - tokenTypeMap.put(newName, tokenTypeMap.get(propertyType)); - updateTypeList(); - getTokenTypeList().setSelectedValue(newName, true); - button.setEnabled(true); - } - })); - button.setEnabled(false); - } - public void initDescriptionContainer() { - getDescriptionContainer().setVisible(false); - } - - public void initHelpButton() { - var button = getHelpButton(); - button.addActionListener( - e -> - EventQueue.invokeLater( - () -> { - JPanel helpText = getDescriptionContainer(); - helpText.setVisible(!helpText.isVisible()); - })); - button.setEnabled(true); - } - - public void initPropertyTable() { - var propertyTable = getTokenPropertiesTable(); - propertyTable.setModel(new TokenPropertiesTableModel()); - propertyTable.setDefaultEditor( - LargeEditableText.class, new TextFieldEditorButtonTableCellEditor()); - propertyTable - .getSelectionModel() - .addListSelectionListener( - e -> { - if (e.getValueIsAdjusting()) { - return; - } - - var deleteButton = getPropertyDeleteButton(); - deleteButton.setEnabled(getTokenPropertiesTable().getSelectedRow() >= 0); - - var moveUpButton = getPropertyMoveUpButton(); - moveUpButton.setEnabled(getTokenPropertiesTable().getSelectedRow() > 0); - - var moveDownButton = getPropertyMoveDownButton(); - // Note: this works even if selection is empty (getSelectedRow() == -1). - moveDownButton.setEnabled( - getTokenPropertiesTable().getSelectedRow() - < getTokenPropertiesTable().getRowCount() - 1); - }); - } - - public void initTokenTypeName() { - var field = getTokenTypeName(); - field.setEditable(false); - field.addActionListener( - event -> { - int option = - JOptionPane.showConfirmDialog( - this, - I18N.getText("campaignPropertiesDialog.tokenTypeNameChangeWarning"), - I18N.getText("campaignPropertiesDialog.tokenTypeNameChangeTitle"), - JOptionPane.OK_CANCEL_OPTION, - JOptionPane.WARNING_MESSAGE); - if (option == JOptionPane.OK_OPTION) { - var ttList = getTokenTypeList(); - var oldName = (String) ttList.getSelectedValue(); - var newName = field.getText(); - tokenTypeMap.put(newName, tokenTypeMap.remove(oldName)); - tokenTypeStatSheetMap.put(newName, tokenTypeStatSheetMap.remove(oldName)); - ttList.setSelectedValue(newName, true); - updateExistingTokenTypes(oldName, newName); - } - }); - } - - private void updateExistingTokenTypes(String oldName, String newName) { - if (oldName == null || newName == null || oldName.equals(newName)) { - return; + // default value + // had to do this here since the short name is not built + // to take advantage of multiple opening/closing parenthesis + // in a single property line + int indexDefault = line.indexOf(':'); + if (indexDefault > 0) { + String defaultVal = line.substring(indexDefault + 1).trim(); + if (defaultVal.length() > 0) { + property.setDefaultValue(defaultVal); + } + + // remove the default value from the end of the string... + line = line.substring(0, indexDefault); } - if (defaultPropertyType.equals(oldName)) { - defaultPropertyType = newName; + // Suffix + // (Really should handle nested parens here) + int index = line.indexOf('('); + if (index > 0) { + int indexClose = line.lastIndexOf(')'); + // Check for unenclosed parentheses. Fix #1575. + if (indexClose < index) { + MapTool.showError(I18N.getText("CampaignPropertyDialog.error.parenthesis", line)); + throw new IllegalArgumentException("Missing parenthesis"); + } + String shortName = line.substring(index + 1, indexClose).trim(); + if (shortName.length() > 0) { + property.setShortName(shortName); + } + line = line.substring(0, index); } - renameTypes.put(oldName, newName); - } - - public void initTypeList() { - - getTokenTypeList() - .addListSelectionListener( - e -> { - if (e.getValueIsAdjusting()) { - return; - } - - var propertyType = - getTokenTypeList().getSelectedValue() == null - ? null - : getTokenTypeList().getSelectedValue().toString(); - - finalizeCellEditing(); - - if (propertyType == null) { - reset(); - getPropertyAddButton().setEnabled(false); - getTypeDeleteButton().setEnabled(false); - getTypeDuplicateButton().setEnabled(false); - getTokenTypeName().setEditable(false); - getStatSheetComboBox().setEnabled(false); - getStatSheetLocationComboBox().setEnabled(false); - getTypeSetAsDefault().setEnabled(false); - } else { - bind((String) getTokenTypeList().getSelectedValue()); - getPropertyAddButton().setEnabled(true); - getTypeDuplicateButton().setEnabled(true); - getTokenTypeName().setEditable(true); - // Can't delete the default property - if (propertyType.equals(defaultPropertyType)) { - getTypeDeleteButton().setEnabled(false); - } else { - getTypeDeleteButton().setEnabled(true); - } - getStatSheetComboBox().setEnabled(true); - getStatSheetLocationComboBox().setEnabled(true); - populateStatSheetComboBoxes(propertyType); - if (!propertyType.equals(defaultPropertyType)) { - getTypeSetAsDefault().setEnabled(true); - } else { - getTypeSetAsDefault().setEnabled(false); - } - } - }); - getTokenTypeList().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - getTokenTypeList().setCellRenderer(new TokenTypeCellRenderer()); - } - - private void populateStatSheetComboBoxes(String propertyType) { - var combo = getStatSheetComboBox(); - combo.removeAllItems(); - var ssManager = new StatSheetManager(); - ssManager.getStatSheets(propertyType).stream() - .sorted(Comparator.comparing(StatSheet::description)) - .forEach(ss -> combo.addItem(ss)); - var statSheetProperty = tokenTypeStatSheetMap.get(propertyType); - String id; - if (statSheetProperty == null) { - id = ssManager.getDefaultStatSheetId(); - tokenTypeStatSheetMap.put( - propertyType, - new StatSheetProperties( - ssManager.getDefaultStatSheetId(), StatSheetLocation.BOTTOM_LEFT)); + line = line.trim(); + property.setName(line); + // Since property names are not case-sensitive, let's make sure that we don't + // already have this name represented somewhere in the list. + String old = caseCheck.get(line); + if (old != null) { + // Perhaps these properties should produce warnings at all, but what it someone + // is actually using them as property names! + if (old.startsWith("---")) + errlog.add( + I18N.getText("msg.error.mtprops.properties.duplicateComment", original, old)); + else errlog.add(I18N.getText("msg.error.mtprops.properties.duplicate", original, old)); } else { - id = statSheetProperty.id(); + propertyList.add(property); + caseCheck.put(line, original); } - combo.setSelectedItem(ssManager.getStatSheet(id)); + } - var locationCombo = getStatSheetLocationComboBox(); - locationCombo.setSelectedItem(tokenTypeStatSheetMap.get(propertyType).location()); + } catch (IOException ioe) { + // If this happens, I'll check into the nearest insane asylum + MapTool.showError("IOException during parsing of properties?!", ioe); } - - public void initStatSheetDetails() { - var locationCombo = getStatSheetLocationComboBox(); - locationCombo.setEnabled(false); - Arrays.stream(StatSheetLocation.values()).forEach(locationCombo::addItem); - locationCombo.addActionListener( - l -> { - if (getStatSheetLocationComboBox().hasFocus()) { // only if user has made change - var location = (StatSheetLocation) locationCombo.getSelectedItem(); - var tokenType = (String) getTokenTypeList().getSelectedValue(); - if (location != null && tokenType != null) { - var id = tokenTypeStatSheetMap.get(tokenType).id(); - tokenTypeStatSheetMap.put(tokenType, new StatSheetProperties(id, location)); - } - } - }); - - var combo = getStatSheetComboBox(); - combo.setEnabled(false); - combo.setRenderer(new StatSheetComboBoxRenderer()); - combo.addActionListener( - l -> { - if (getStatSheetComboBox().hasFocus()) { // Only if user has made change - var ss = (StatSheet) combo.getSelectedItem(); - var tokenType = (String) getTokenTypeList().getSelectedValue(); - if (ss != null && tokenType != null) { - var id = new StatSheetManager().getId(ss); - var location = tokenTypeStatSheetMap.get(tokenType).location(); - tokenTypeStatSheetMap.put(tokenType, new StatSheetProperties(id, location)); - getStatSheetLocationComboBox().setSelectedItem(location); - } - } - }); - } - - private void bind(String type) { - - editingType = type; - - getTokenTypeName().setText(type != null ? type : ""); - var model = getTokenPropertiesTableModel(); - model.setPropertyType(type); + caseCheck.clear(); + if (!errlog.isEmpty()) { + errlog.add(0, I18N.getText("msg.error.mtprops.properties.title", editingType)); + errlog.add(I18N.getText("msg.error.mtprops.properties.ending")); + MapTool.showFeedback(errlog.toArray()); + errlog.clear(); + throw new IllegalArgumentException(); // Don't save the properties... } + return propertyList; + } - private void reset() { - - bind((String) null); - } - - private void updateTypeList() { - getTokenTypeList().setModel(new TypeListModel()); - getTokenPropertiesTableModel().setPropertyTypeMap(tokenTypeMap); - } - - private String compileTokenProperties(List propertyList) { - - // Sanity - if (propertyList == null) { - return ""; - } - - StringBuilder builder = new StringBuilder(); - - for (TokenProperty property : propertyList) { - if (property.isShowOnStatSheet()) { - builder.append("*"); - } - if (property.isOwnerOnly()) { - builder.append("@"); - } - if (property.isGMOnly()) { - builder.append("#"); - } - builder.append(property.getName()); - if (property.getShortName() != null) { - builder.append(" (").append(property.getShortName()).append(")"); - } - if (property.getDefaultValue() != null) { - builder.append(":").append(property.getDefaultValue()); - } - builder.append("\n"); - } - - return builder.toString(); - } + public void prettify() { - /** - * Given a string (normally from the JTextArea which holds the properties for a Property Type) - * this method converts those lines into a List of EditTokenProperty objects. It checks for - * duplicates along the way, ignoring any it finds. (Should produce a list of warnings to indicate - * which ones are duplicates. See the Light/Sight code for examples.) - * - * @param propertyText - * @return + /* fix text areas to look like labels + * dig down to the appropriate container level + * then set the backgrounds to transparent */ - private List parseTokenProperties(String propertyText) - throws IllegalArgumentException { - List propertyList = new ArrayList(); - BufferedReader reader = new BufferedReader(new StringReader(propertyText)); - CaseInsensitiveHashMap caseCheck = new CaseInsensitiveHashMap(); - List errlog = new LinkedList(); - try { - String original, line; - while ((original = reader.readLine()) != null) { - line = original = original.trim(); - if (line.length() == 0) { - continue; - } - - TokenProperty property = new TokenProperty(); - - // Prefix - while (true) { - if (line.startsWith("*")) { - property.setShowOnStatSheet(true); - line = line.substring(1); - continue; - } - if (line.startsWith("@")) { - property.setOwnerOnly(true); - line = line.substring(1); - continue; - } - if (line.startsWith("#")) { - property.setGMOnly(true); - line = line.substring(1); - continue; - } - - // Ran out of special characters - break; - } - - // default value - // had to do this here since the short name is not built - // to take advantage of multiple opening/closing parenthesis - // in a single property line - int indexDefault = line.indexOf(':'); - if (indexDefault > 0) { - String defaultVal = line.substring(indexDefault + 1).trim(); - if (defaultVal.length() > 0) { - property.setDefaultValue(defaultVal); - } - - // remove the default value from the end of the string... - line = line.substring(0, indexDefault); - } - // Suffix - // (Really should handle nested parens here) - int index = line.indexOf('('); - if (index > 0) { - int indexClose = line.lastIndexOf(')'); - // Check for unenclosed parentheses. Fix #1575. - if (indexClose < index) { - MapTool.showError(I18N.getText("CampaignPropertyDialog.error.parenthesis", line)); - throw new IllegalArgumentException("Missing parenthesis"); - } - String shortName = line.substring(index + 1, indexClose).trim(); - if (shortName.length() > 0) { - property.setShortName(shortName); - } - line = line.substring(0, index); - } - line = line.trim(); - property.setName(line); - // Since property names are not case-sensitive, let's make sure that we don't - // already have this name represented somewhere in the list. - String old = caseCheck.get(line); - if (old != null) { - // Perhaps these properties should produce warnings at all, but what it someone - // is actually using them as property names! - if (old.startsWith("---")) - errlog.add( - I18N.getText("msg.error.mtprops.properties.duplicateComment", original, old)); - else errlog.add(I18N.getText("msg.error.mtprops.properties.duplicate", original, old)); - } else { - propertyList.add(property); - caseCheck.put(line, original); - } - } + JPanel jPanel = (JPanel) super.getComponent("descriptionContainer"); + List jPanels = + Arrays.stream(jPanel.getComponents()).filter(c -> c instanceof JPanel).toList(); - } catch (IOException ioe) { - // If this happens, I'll check into the nearest insane asylum - MapTool.showError("IOException during parsing of properties?!", ioe); - } - caseCheck.clear(); - if (!errlog.isEmpty()) { - errlog.add(0, I18N.getText("msg.error.mtprops.properties.title", editingType)); - errlog.add(I18N.getText("msg.error.mtprops.properties.ending")); - MapTool.showFeedback(errlog.toArray()); - errlog.clear(); - throw new IllegalArgumentException(); // Don't save the properties... - } - return propertyList; + Color transparent = new Color(0, 0, 0, 1); + for (Component panel : jPanels) { + JPanel jp = (JPanel) panel; + Component[] components = jp.getComponents(); + Arrays.stream(components).toList().forEach(c -> c.setBackground(transparent)); } - public void prettify() { - - /* fix text areas to look like labels - * dig down to the appropriate container level - * then set the backgrounds to transparent - */ - JPanel jPanel = (JPanel) super.getComponent("descriptionContainer"); - List jPanels = - Arrays.stream(jPanel.getComponents()).filter(c -> c instanceof JPanel).toList(); - - Color transparent = new Color(0, 0, 0, 1); - for (Component panel : jPanels) { - JPanel jp = (JPanel) panel; - Component[] components = jp.getComponents(); - Arrays.stream(components).toList().forEach(c -> c.setBackground(transparent)); - } - - JTable propertyTable = getTokenPropertiesTable(); + JTable propertyTable = getTokenPropertiesTable(); /* prettify - take cell background colour and adjust the luminance for cell contrast. change the hue and saturation for the grid line colour */ - Color bg, bgSmall, gridColour; - bg = propertyTable.getTableHeader().getComponent(0).getBackground(); // get background colour - float[] hsbComponents = new float[3]; - Color.RGBtoHSB(bg.getRed(), bg.getGreen(), bg.getBlue(), hsbComponents); // convert to HSB - - boolean lighten = hsbComponents[2] < 0.5f; // to determine direction of change - hsbComponents[2] = - lighten - ? hsbComponents[2] + 0.015f - : hsbComponents[2] - 0.025f; // small change in brilliance - bgSmall = new Color(Color.HSBtoRGB(hsbComponents[0], hsbComponents[1], hsbComponents[2])); - - hsbComponents[2] = - lighten - ? hsbComponents[2] + 0.04f - : hsbComponents[2] - 0.02f; // bigger change in brilliance - bg = new Color(Color.HSBtoRGB(hsbComponents[0], hsbComponents[1], hsbComponents[2])); - - hsbComponents[0] = - hsbComponents[0] < 0.5 - ? hsbComponents[0] + 0.5f - : hsbComponents[0] - 0.5f; // change hue 180 degrees - hsbComponents[1] = - hsbComponents[1] < 0.25 - ? hsbComponents[1] + 0.25f // increase saturation if it is low - : hsbComponents[1]; - gridColour = new Color(Color.HSBtoRGB(hsbComponents[0], hsbComponents[1], hsbComponents[2])); - - DefaultTableCellRenderer cellRenderer = - new DefaultTableCellRenderer(); // cell renderer for contrasting cells - cellRenderer.setBackground(bgSmall); - cellRenderer.setHorizontalAlignment(DefaultTableCellRenderer.LEFT); - - DefaultTableCellRenderer headerRenderer = - new DefaultTableCellRenderer(); // cell renderer for contrasting headings - headerRenderer.setBackground(bg); - headerRenderer.setHorizontalAlignment(DefaultTableCellRenderer.CENTER); - headerRenderer.setVerticalAlignment(SwingConstants.TOP); - - DefaultTableCellRenderer headerRenderer2 = new DefaultTableCellRenderer(); - headerRenderer2.setVerticalAlignment(SwingConstants.TOP); - headerRenderer2.setHorizontalAlignment(SwingConstants.CENTER); - - DefaultTableCellRenderer rowHeaderRenderer = - new DefaultTableCellRenderer(); // cell renderer for contrasting headings - headerRenderer.setBackground(bg); - headerRenderer.setHorizontalAlignment(DefaultTableCellRenderer.LEFT); - - propertyTable.setGridColor(gridColour); - propertyTable.setIntercellSpacing(new Dimension(2, 2)); - propertyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - propertyTable.setShowHorizontalLines(true); - propertyTable.getTableHeader().setResizingAllowed(true); - propertyTable.setFillsViewportHeight(true); - - for (int i = 0; i < propertyTable.getColumnCount(); i++) { - - switch (i) { // set column shading - case 0, 2, 4, 6 -> propertyTable - .getColumnModel() - .getColumn(i) - .setHeaderRenderer(headerRenderer); - case 1, 3 -> { - propertyTable.getColumnModel().getColumn(i).setCellRenderer(cellRenderer); - propertyTable.getColumnModel().getColumn(i).setHeaderRenderer(headerRenderer2); - } - } - - switch (i) { // set column sizes - case 0, 2, 3 -> { - propertyTable.getColumnModel().getColumn(i).setMinWidth(60); - propertyTable.getColumnModel().getColumn(i).setPreferredWidth(80); - } - case 1 -> { - propertyTable.getColumnModel().getColumn(i).setMinWidth(45); - propertyTable.getColumnModel().getColumn(i).setMaxWidth(100); - propertyTable.getColumnModel().getColumn(i).setPreferredWidth(55); - } - case 4, 5, 6 -> { - propertyTable.getColumnModel().getColumn(i).setMinWidth(50); - propertyTable.getColumnModel().getColumn(i).setMaxWidth(80); - propertyTable.getColumnModel().getColumn(i).setPreferredWidth(50); - } - } + Color bg, bgSmall, gridColour; + bg = propertyTable.getTableHeader().getComponent(0).getBackground(); // get background colour + float[] hsbComponents = new float[3]; + Color.RGBtoHSB(bg.getRed(), bg.getGreen(), bg.getBlue(), hsbComponents); // convert to HSB + + boolean lighten = hsbComponents[2] < 0.5f; // to determine direction of change + hsbComponents[2] = + lighten + ? hsbComponents[2] + 0.015f + : hsbComponents[2] - 0.025f; // small change in brilliance + bgSmall = new Color(Color.HSBtoRGB(hsbComponents[0], hsbComponents[1], hsbComponents[2])); + + hsbComponents[2] = + lighten + ? hsbComponents[2] + 0.04f + : hsbComponents[2] - 0.02f; // bigger change in brilliance + bg = new Color(Color.HSBtoRGB(hsbComponents[0], hsbComponents[1], hsbComponents[2])); + + hsbComponents[0] = + hsbComponents[0] < 0.5 + ? hsbComponents[0] + 0.5f + : hsbComponents[0] - 0.5f; // change hue 180 degrees + hsbComponents[1] = + hsbComponents[1] < 0.25 + ? hsbComponents[1] + 0.25f // increase saturation if it is low + : hsbComponents[1]; + gridColour = new Color(Color.HSBtoRGB(hsbComponents[0], hsbComponents[1], hsbComponents[2])); + + DefaultTableCellRenderer cellRenderer = + new DefaultTableCellRenderer(); // cell renderer for contrasting cells + cellRenderer.setBackground(bgSmall); + cellRenderer.setHorizontalAlignment(DefaultTableCellRenderer.LEFT); + + DefaultTableCellRenderer headerRenderer = + new DefaultTableCellRenderer(); // cell renderer for contrasting headings + headerRenderer.setBackground(bg); + headerRenderer.setHorizontalAlignment(DefaultTableCellRenderer.CENTER); + headerRenderer.setVerticalAlignment(SwingConstants.TOP); + + DefaultTableCellRenderer headerRenderer2 = new DefaultTableCellRenderer(); + headerRenderer2.setVerticalAlignment(SwingConstants.TOP); + headerRenderer2.setHorizontalAlignment(SwingConstants.CENTER); + + DefaultTableCellRenderer rowHeaderRenderer = + new DefaultTableCellRenderer(); // cell renderer for contrasting headings + headerRenderer.setBackground(bg); + headerRenderer.setHorizontalAlignment(DefaultTableCellRenderer.LEFT); + + propertyTable.setGridColor(gridColour); + propertyTable.setIntercellSpacing(new Dimension(2, 2)); + propertyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + propertyTable.setShowHorizontalLines(true); + propertyTable.getTableHeader().setResizingAllowed(true); + propertyTable.setFillsViewportHeight(true); + + for (int i = 0; i < propertyTable.getColumnCount(); i++) { + + switch (i) { // set column shading + case 0, 2, 4, 6 -> propertyTable + .getColumnModel() + .getColumn(i) + .setHeaderRenderer(headerRenderer); + case 1, 3 -> { + propertyTable.getColumnModel().getColumn(i).setCellRenderer(cellRenderer); + propertyTable.getColumnModel().getColumn(i).setHeaderRenderer(headerRenderer2); } - Font hFont = propertyTable.getTableHeader().getComponent(0).getFont(); - Dimension headerDim = propertyTable.getTableHeader().getSize(); - headerDim.height = (int) (hFont.getSize() * 3.41); - propertyTable.getTableHeader().setPreferredSize(headerDim); - } + } - private class TypeListModel extends AbstractListModel { - public Object getElementAt(int index) { - List names = new ArrayList(tokenTypeMap.keySet()); - Collections.sort(names); - return names.get(index); + switch (i) { // set column sizes + case 0, 2, 3 -> { + propertyTable.getColumnModel().getColumn(i).setMinWidth(60); + propertyTable.getColumnModel().getColumn(i).setPreferredWidth(80); } - - public int getSize() { - return tokenTypeMap.size(); + case 1 -> { + propertyTable.getColumnModel().getColumn(i).setMinWidth(45); + propertyTable.getColumnModel().getColumn(i).setMaxWidth(100); + propertyTable.getColumnModel().getColumn(i).setPreferredWidth(55); } - } - - /** - * Gets the Token Property Type rename operations that have occurred. - * - * @return a {@link Map} of renames. - */ - public SortedMap getRenameTypes() { - return renameTypes; - } - - /** A List cell renderer that calls out default property type. */ - private class TokenTypeCellRenderer extends JLabel implements ListCellRenderer { - - public TokenTypeCellRenderer() { - setOpaque(true); - } - - @Override - public Component getListCellRendererComponent( - JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - - var val = value.toString(); - if (val.equals(defaultPropertyType)) { - setText( - "" - + val - + "     (" - + I18N.getString("TokenPropertiesPanel.defaultPropertyType") - + ")"); - } else { - setText("" + val + ""); - } - - if (isSelected) { - setBackground(list.getSelectionBackground()); - setForeground(list.getSelectionForeground()); - } else { - setBackground(list.getBackground()); - setForeground(list.getForeground()); - } - - return this; + case 4, 5, 6 -> { + propertyTable.getColumnModel().getColumn(i).setMinWidth(50); + propertyTable.getColumnModel().getColumn(i).setMaxWidth(80); + propertyTable.getColumnModel().getColumn(i).setPreferredWidth(50); } - } + } + } + Font hFont = propertyTable.getTableHeader().getComponent(0).getFont(); + Dimension headerDim = propertyTable.getTableHeader().getSize(); + headerDim.height = (int) (hFont.getSize() * 3.41); + propertyTable.getTableHeader().setPreferredSize(headerDim); + } + + private class TypeListModel extends AbstractListModel { + public Object getElementAt(int index) { + List names = new ArrayList(tokenTypeMap.keySet()); + Collections.sort(names); + return names.get(index); + } + + public int getSize() { + return tokenTypeMap.size(); + } + } + + /** + * Gets the Token Property Type rename operations that have occurred. + * + * @return a {@link Map} of renames. + */ + public SortedMap getRenameTypes() { + return renameTypes; + } + + /** A List cell renderer that calls out default property type. */ + private class TokenTypeCellRenderer extends JLabel implements ListCellRenderer { + + public TokenTypeCellRenderer() { + setOpaque(true); + } + + @Override + public Component getListCellRendererComponent( + JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + + var val = value.toString(); + if (val.equals(defaultPropertyType)) { + setText( + "" + + val + + "     (" + + I18N.getString("TokenPropertiesPanel.defaultPropertyType") + + ")"); + } else { + setText("" + val + ""); + } + + if (isSelected) { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } else { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + + return this; + } + } } diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesTableModel.java index 979ecd1ee4..fad4743b20 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesTableModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesTableModel.java @@ -118,7 +118,7 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { var tokenProperty = properties.get(rowIndex); return switch (columnIndex) { case 5, 6 -> tokenProperty - .isShowOnStatSheet(); // GM, Owner only editable if show on stat sheet is set + .isShowOnStatSheet(); // GM, Owner only editable if show on stat sheet is set default -> true; }; } From 710a5e92747380498bc95e87fa21b666adaccdb6 Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Wed, 2 Oct 2024 23:31:38 +0800 Subject: [PATCH 043/146] Added function getMacroHotkeys() --- .../client/functions/MacroFunctions.java | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java b/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java index 052f379765..042787348d 100644 --- a/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java @@ -18,13 +18,14 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; +import java.util.*; +import javax.swing.*; import net.rptools.lib.MD5Key; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.client.functions.json.JSONMacroFunctions; +import net.rptools.maptool.client.ui.macrobuttons.MacroButtonHotKeyManager; +import net.rptools.maptool.client.ui.macrobuttons.buttons.MacroButton; import net.rptools.maptool.client.ui.macrobuttons.panels.AbstractMacroPanel; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.MacroButtonProperties; @@ -61,6 +62,7 @@ private MacroFunctions() { "setMacroProps", "getMacros", "getMacroProps", + "getMacroHotkeys", "getMacroIndexes", "getMacroIndices", "getMacroName", @@ -218,7 +220,13 @@ public Object childEvaluate( } else if (functionName.equalsIgnoreCase("getMacroButtonIndex")) { return BigDecimal.valueOf(MapTool.getParser().getMacroButtonIndex()); - + } else if (functionName.equalsIgnoreCase("getMacroHotkeys")) { + FunctionUtil.checkNumberParam(functionName, parameters, 0, 1); + String delim = "json"; + if (!parameters.isEmpty()) { + delim = parameters.getFirst().toString(); + } + return getMacroHotkeys(delim); } else if (functionName.equalsIgnoreCase("getMacroGroup")) { FunctionUtil.checkNumberParam(functionName, parameters, 1, 4); String group = parameters.get(0).toString(); @@ -252,6 +260,30 @@ public Object childEvaluate( throw new ParserException(I18N.getText(KEY_UNKNOWN_MACRO, functionName)); } + private Object getMacroHotkeys(String delim) { + Map keyStrokeMap = MacroButtonHotKeyManager.getKeyStrokeMap(); + JsonObject keyMacroObject = new JsonObject(); + + for (KeyStroke ks : keyStrokeMap.keySet()) { + MacroButton btn = keyStrokeMap.get(ks); + String address = btn.getProperties().getLabel() + "@"; + switch (btn.getPanelClass()) { + case "GlobalPanel", "globalpanel" -> address += "global"; + case "CampaignPanel", "campaignpanel" -> address += "campaign"; + case "GmPanel", "gmpanel" -> address += "gm"; + default -> address += btn.getToken().getName(); + } + keyMacroObject.addProperty(ks.toString(), address); + } + if (delim.equalsIgnoreCase("json")) { + return keyMacroObject; + } else { + return JSONMacroFunctions.getInstance() + .getJsonObjectFunctions() + .toStringProp(keyMacroObject, delim); + } + } + /** * Campaign version of hasMacro(). * From 0df96693203b4b18183b92caedbcfcc384e1f212 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 3 Oct 2024 22:44:41 -0700 Subject: [PATCH 044/146] Fix light offset for beams to measure counterclockwise as for cones Isometric maps already did this, but all other maps (including gridless) measured clockwise for beams, counterclockwise for cones. --- src/main/java/net/rptools/maptool/model/Grid.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/rptools/maptool/model/Grid.java b/src/main/java/net/rptools/maptool/model/Grid.java index d319130f53..91d3754509 100644 --- a/src/main/java/net/rptools/maptool/model/Grid.java +++ b/src/main/java/net/rptools/maptool/model/Grid.java @@ -454,7 +454,7 @@ public void setSize(int size) { visibleArea = new Area( - AffineTransform.getRotateInstance(Math.toRadians(offsetAngle + tokenFacingAngle)) + AffineTransform.getRotateInstance(Math.toRadians(tokenFacingAngle - offsetAngle)) .createTransformedShape(lineShape)); } case CONE -> { From e75d474001cb3ef6e0ab08eb6b8b508b7ca62d7c Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Fri, 4 Oct 2024 19:55:57 +0800 Subject: [PATCH 045/146] Added static function to MacroButtonHotKeyManager::isHotkeyAssigned Changed macro functions assigning hotkeys to substitute "none" where the hotkey is already assigned. --- .../rptools/maptool/client/functions/MacroFunctions.java | 6 ++++++ .../client/ui/macrobuttons/MacroButtonHotKeyManager.java | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java b/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java index 042787348d..0651ec04cf 100644 --- a/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java @@ -396,6 +396,9 @@ public void setMacroProps(MacroButtonProperties mbp, String propString, String d } else if ("group".equalsIgnoreCase(key)) { mbp.setGroup(value); } else if ("hotkey".equalsIgnoreCase(key)) { + if(MacroButtonHotKeyManager.isHotkeyAssigned(value)) { + value = MacroButtonHotKeyManager.HOTKEYS[0]; + } mbp.setHotKey(value); } else if ("includeLabel".equalsIgnoreCase(key)) { mbp.setIncludeLabel(boolVal(value)); @@ -1138,6 +1141,9 @@ private MacroButtonProperties macroButtonPropertiesFromJSON( } else if ("group".equalsIgnoreCase(key)) { mbp.setGroup(value); } else if ("hotkey".equalsIgnoreCase(key)) { + if(MacroButtonHotKeyManager.isHotkeyAssigned(value)) { + value = MacroButtonHotKeyManager.HOTKEYS[0]; + } mbp.setHotKey(value); } else if ("includeLabel".equalsIgnoreCase(key)) { mbp.setIncludeLabel(boolVal(value)); diff --git a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/MacroButtonHotKeyManager.java b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/MacroButtonHotKeyManager.java index 374afb4f71..af7e7631ae 100644 --- a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/MacroButtonHotKeyManager.java +++ b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/MacroButtonHotKeyManager.java @@ -82,7 +82,9 @@ public class MacroButtonHotKeyManager { private static Map buttonsByKeyStroke = new HashMap(); private MacroButton macroButton; - + public static boolean isHotkeyAssigned(String hotkey){ + return buttonsByKeyStroke.containsKey(hotkey); + } public MacroButtonHotKeyManager(MacroButton macroButton) { this.macroButton = macroButton; } From 2b923447bbc800b28912b8997a7733e07a5f4719 Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Fri, 4 Oct 2024 20:01:08 +0800 Subject: [PATCH 046/146] Added static function to MacroButtonHotKeyManager::isHotkeyAssigned le sigh --- .../net/rptools/maptool/client/functions/MacroFunctions.java | 4 ++-- .../client/ui/macrobuttons/MacroButtonHotKeyManager.java | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java b/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java index 0651ec04cf..89a9a1011d 100644 --- a/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java @@ -396,7 +396,7 @@ public void setMacroProps(MacroButtonProperties mbp, String propString, String d } else if ("group".equalsIgnoreCase(key)) { mbp.setGroup(value); } else if ("hotkey".equalsIgnoreCase(key)) { - if(MacroButtonHotKeyManager.isHotkeyAssigned(value)) { + if (MacroButtonHotKeyManager.isHotkeyAssigned(value)) { value = MacroButtonHotKeyManager.HOTKEYS[0]; } mbp.setHotKey(value); @@ -1141,7 +1141,7 @@ private MacroButtonProperties macroButtonPropertiesFromJSON( } else if ("group".equalsIgnoreCase(key)) { mbp.setGroup(value); } else if ("hotkey".equalsIgnoreCase(key)) { - if(MacroButtonHotKeyManager.isHotkeyAssigned(value)) { + if (MacroButtonHotKeyManager.isHotkeyAssigned(value)) { value = MacroButtonHotKeyManager.HOTKEYS[0]; } mbp.setHotKey(value); diff --git a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/MacroButtonHotKeyManager.java b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/MacroButtonHotKeyManager.java index af7e7631ae..d2726c70e0 100644 --- a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/MacroButtonHotKeyManager.java +++ b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/MacroButtonHotKeyManager.java @@ -82,9 +82,11 @@ public class MacroButtonHotKeyManager { private static Map buttonsByKeyStroke = new HashMap(); private MacroButton macroButton; - public static boolean isHotkeyAssigned(String hotkey){ + + public static boolean isHotkeyAssigned(String hotkey) { return buttonsByKeyStroke.containsKey(hotkey); } + public MacroButtonHotKeyManager(MacroButton macroButton) { this.macroButton = macroButton; } From 4df96108909d6e74d34350e736b1f61199544a8f Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Sun, 6 Oct 2024 03:48:32 +0800 Subject: [PATCH 047/146] New translations i18n.properties (Japanese) --- .../net/rptools/maptool/language/i18n_ja_JP.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties b/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties index 5c03310dce..6cdb10a17d 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties @@ -522,8 +522,8 @@ Preferences.developer.showAiDebugging.label = AIのデバッグを有効に Preferences.developer.showAiDebugging.tooltip = 有効にした場合、自動経路の算出中にA*にて算出されたf・g・hのコストが、障壁レイヤーに阻まれた移動に加えて、ラベルに含まれるようになります。 Preferences.developer.ignoreGridShapeCache.label = マス目形状のキャッシュを無視 Preferences.developer.ignoreGridShapeCache.tooltip = 有効にした場合、マス目形状は要求される度に再計算されます。 -Preferences.developer.debugTokenDragging.label = Enable token drag debugging -Preferences.developer.debugTokenDragging.tooltip = When enabled, highlights key points used during token drags, such as anchor points. +Preferences.developer.debugTokenDragging.label = トークンのドラッグのデバッグを有効にする +Preferences.developer.debugTokenDragging.tooltip = 有効にすると、トークンのドラッグ時に使用される、アンカーポイントなどの主要なポイントをハイライトします。 Preferences.developer.info.developerOptionsInUsePost = これが意図したものではない場合、{0} → {1} → {2} のタブに行き、その場所にあるオプションを無効にしてください。 Preferences.tab.interactions = ゲーム進行 Preferences.label.maps.fow = 新規地図は不明領域を持つ From e662f709b6e3d4ffa55f5f30f07a84224305c4aa Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Sat, 5 Oct 2024 14:14:42 -0700 Subject: [PATCH 048/146] Fix hex light construction The proportions and center were just all wrong. `Grid.getShapedArea()` should always treat (0, 0) as its origin for the light, so no adjustments are needed to account for the grid or token shape. Also fixed a bug where the hex radius would measure to the vertices instead of the edges (compare with square lights that measure to the edges). Since we're modifying the hex code anyways, also refactored to be more direct. We don't need trig - there's only six points and they're always the same up to scale. Well, until the day we account for stretched hex grids. --- .../java/net/rptools/maptool/model/Grid.java | 39 ++++++------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/src/main/java/net/rptools/maptool/model/Grid.java b/src/main/java/net/rptools/maptool/model/Grid.java index 91d3754509..c90d13bf7a 100644 --- a/src/main/java/net/rptools/maptool/model/Grid.java +++ b/src/main/java/net/rptools/maptool/model/Grid.java @@ -479,16 +479,7 @@ public void setSize(int size) { visibleArea.add(new Area(footprintPart)); } case HEX -> { - double x = footprint.getCenterX(); - double y = footprint.getCenterY(); - - double footprintWidth = footprint.getWidth(); - double footprintHeight = footprint.getHeight(); - double adjustment = Math.min(footprintWidth, footprintHeight); - x -= adjustment / 2; - y -= adjustment / 2; - - visibleArea = createHex(x, y, visionRange, 0); + visibleArea = createHex(visionRange); } default -> { log.error("Unhandled shape {}; treating as a circle", shape); @@ -527,25 +518,19 @@ public double cellDistance(CellPoint cellA, CellPoint cellB, WalkerMetric wmetri return distance; } - protected Area createHex(double x, double y, double radius, double rotation) { - GeneralPath hexPath = new GeneralPath(); + protected Area createHex(double inRadius) { + double radius = inRadius * 2 / Math.sqrt(3); - for (int i = 0; i < 6; i++) { - if (i == 0) { - hexPath.moveTo( - x + radius * Math.cos(i * 2 * Math.PI / 6), y + radius * Math.sin(i * 2 * Math.PI / 6)); - } else { - hexPath.lineTo( - x + radius * Math.cos(i * 2 * Math.PI / 6), y + radius * Math.sin(i * 2 * Math.PI / 6)); - } - } + var hexPath = new Path2D.Double(); + hexPath.moveTo(radius, 0); + hexPath.lineTo(radius * 0.5, inRadius); + hexPath.lineTo(-radius * 0.5, inRadius); + hexPath.lineTo(-radius, 0); + hexPath.lineTo(-radius * 0.5, -inRadius); + hexPath.lineTo(radius * 0.5, -inRadius); + hexPath.closePath(); - if (rotation != 0) { - AffineTransform atArea = AffineTransform.getRotateInstance(rotation); - return new Area(atArea.createTransformedShape(hexPath)); - } else { - return new Area(hexPath); - } + return new Area(hexPath); } private void fireGridChanged() { From 1be39bf0937534e645f36435c21cb5a09d4def34 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Sun, 6 Oct 2024 16:44:58 -0700 Subject: [PATCH 049/146] Do not show local player in Connections window or status panel --- src/main/java/net/rptools/maptool/client/MapToolClient.java | 4 +++- .../net/rptools/maptool/client/events/PlayerConnected.java | 2 +- .../maptool/client/swing/PlayersLoadingStatusBar.java | 6 +++++- .../client/ui/connections/ClientConnectionPanel.java | 4 ++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/MapToolClient.java b/src/main/java/net/rptools/maptool/client/MapToolClient.java index 62c3e4a837..4540c9b5d4 100644 --- a/src/main/java/net/rptools/maptool/client/MapToolClient.java +++ b/src/main/java/net/rptools/maptool/client/MapToolClient.java @@ -211,7 +211,9 @@ public List getPlayerList() { public void addPlayer(Player player) { if (!playerList.contains(player)) { playerList.add(player); - new MapToolEventBus().getMainEventBus().post(new PlayerConnected(player)); + new MapToolEventBus() + .getMainEventBus() + .post(new PlayerConnected(player, this.player.equals(player))); playerDatabase.playerSignedIn(player); playerList.sort((arg0, arg1) -> arg0.getName().compareToIgnoreCase(arg1.getName())); diff --git a/src/main/java/net/rptools/maptool/client/events/PlayerConnected.java b/src/main/java/net/rptools/maptool/client/events/PlayerConnected.java index 926daab8de..bedd35f374 100644 --- a/src/main/java/net/rptools/maptool/client/events/PlayerConnected.java +++ b/src/main/java/net/rptools/maptool/client/events/PlayerConnected.java @@ -16,4 +16,4 @@ import net.rptools.maptool.model.player.Player; -public record PlayerConnected(Player player) {} +public record PlayerConnected(Player player, boolean isLocal) {} diff --git a/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java b/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java index 7416db36b4..43363c3279 100644 --- a/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java +++ b/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java @@ -16,6 +16,7 @@ import com.google.common.eventbus.Subscribe; import java.awt.Dimension; +import java.util.ArrayList; import javax.swing.Icon; import javax.swing.JLabel; import net.rptools.maptool.client.MapTool; @@ -46,7 +47,10 @@ public PlayersLoadingStatusBar() { } private void refreshCount() { - var players = MapTool.getPlayerList(); + var localPlayer = MapTool.getPlayer(); + var players = new ArrayList<>(MapTool.getPlayerList()); + players.remove(localPlayer); + var total = players.size(); var loaded = players.stream().filter(x -> x.getLoaded()).count(); diff --git a/src/main/java/net/rptools/maptool/client/ui/connections/ClientConnectionPanel.java b/src/main/java/net/rptools/maptool/client/ui/connections/ClientConnectionPanel.java index f2a5d95fa1..b930a456bc 100644 --- a/src/main/java/net/rptools/maptool/client/ui/connections/ClientConnectionPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/connections/ClientConnectionPanel.java @@ -111,6 +111,10 @@ public ClientConnectionPanel() { @Subscribe private void onPlayerConnected(PlayerConnected event) { + if (event.isLocal()) { + return; + } + listModel.addElement(event.player()); } From 109a042b2b4742d5c55017a4f4ecf2e60a01288c Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Sun, 6 Oct 2024 16:50:06 -0700 Subject: [PATCH 050/146] Refactor PlayerLoadingStatusBar to keep its own list of players It already subscribes to the necessary events to keep it up-to-date. Also no more need for the self check. --- .../client/swing/PlayersLoadingStatusBar.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java b/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java index 43363c3279..a488df70d8 100644 --- a/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java +++ b/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java @@ -41,30 +41,21 @@ public class PlayersLoadingStatusBar extends JLabel { loadingIcon = RessourceManager.getSmallIcon(Icons.STATUSBAR_PLAYERS_LOADING); } + private final ArrayList players = new ArrayList<>(); + public PlayersLoadingStatusBar() { refreshCount(); new MapToolEventBus().getMainEventBus().register(this); } private void refreshCount() { - var localPlayer = MapTool.getPlayer(); - var players = new ArrayList<>(MapTool.getPlayerList()); - players.remove(localPlayer); - var total = players.size(); var loaded = players.stream().filter(x -> x.getLoaded()).count(); var sb = new StringBuilder(I18N.getText("ConnectionStatusPanel.playersLoadedZone", loaded, total)); - var self = MapTool.getPlayer(); - for (Player player : players) { - // The Player in the list is a seperate entity to the one from MapTool.getPlayer() - // So it doesn't have the correct status data. - if (player.getName().equals(self.getName())) { - player = self; - } var zone = player.getZoneId() == null ? null : MapTool.getCampaign().getZone(player.getZoneId()); @@ -108,6 +99,11 @@ public Dimension getPreferredSize() { @Subscribe private void onPlayerConnected(PlayerConnected event) { + if (event.isLocal()) { + return; + } + players.add(event.player()); + refreshCount(); } @@ -118,11 +114,13 @@ private void onPlayerStatusChanged(PlayerStatusChanged event) { @Subscribe private void onPlayerDisconnected(PlayerDisconnected event) { + players.remove(event.player()); refreshCount(); } @Subscribe private void onServerDisconnected(ServerDisconnected event) { + players.clear(); refreshCount(); } } From be85a5cb50da38f3af2bb465178ec4d3df3c4902 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Mon, 7 Oct 2024 19:40:03 -0700 Subject: [PATCH 051/146] Move non-preference preferences into new AppStatePersisted This new class is for app state that needs to be maintained between runs of MT. It is not for user preferences despite being built on `Preferences` as well. --- .../rptools/maptool/client/AppActions.java | 2 +- .../maptool/client/AppPreferences.java | 183 +------------- .../net/rptools/maptool/client/AppSetup.java | 2 +- .../maptool/client/AppStatePersisted.java | 224 ++++++++++++++++++ .../maptool/client/MRUCampaignManager.java | 4 +- .../net/rptools/maptool/client/MapTool.java | 4 +- .../swing/TopologyModeSelectionPanel.java | 8 +- .../maptool/client/ui/MapToolFrame.java | 2 +- .../ui/addresource/AddResourceDialog.java | 4 +- .../MapPropertiesDialog.java | 4 +- .../java/net/rptools/maptool/model/Zone.java | 4 +- 11 files changed, 244 insertions(+), 197 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/client/AppStatePersisted.java diff --git a/src/main/java/net/rptools/maptool/client/AppActions.java b/src/main/java/net/rptools/maptool/client/AppActions.java index c49300c1b7..c669f699bc 100644 --- a/src/main/java/net/rptools/maptool/client/AppActions.java +++ b/src/main/java/net/rptools/maptool/client/AppActions.java @@ -1195,7 +1195,7 @@ protected void executeAction() { MapTool.showError("msg.error.mustSelectRootGroup"); return; } - AppPreferences.removeAssetRoot(dir.getPath()); + AppStatePersisted.removeAssetRoot(dir.getPath()); assetPanel.removeAssetRoot(dir); } }; diff --git a/src/main/java/net/rptools/maptool/client/AppPreferences.java b/src/main/java/net/rptools/maptool/client/AppPreferences.java index 298b30be10..50609aec8a 100644 --- a/src/main/java/net/rptools/maptool/client/AppPreferences.java +++ b/src/main/java/net/rptools/maptool/client/AppPreferences.java @@ -19,11 +19,6 @@ import java.awt.Graphics2D; import java.awt.RenderingHints; import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import java.util.prefs.Preferences; import net.rptools.maptool.client.ui.theme.RessourceManager; import net.rptools.maptool.client.walker.WalkerMetric; @@ -35,15 +30,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -/** The AppPreferences class is used for managing the preferences of the application. */ +/** Manages and persists user preferences for the application. */ public class AppPreferences { - - /** - * The log variable represents a logger object used for logging messages in the AppPreferences - * class. It is a private static final variable of type Logger. The logger object is obtained - * using the getLogger method from the LogManager class, specifying the AppPreferences class as - * the logging context. - */ private static final Logger log = LogManager.getLogger(AppPreferences.class); /** @@ -56,7 +44,8 @@ public class AppPreferences { * *

This variable is used to access and modify user preferences throughout the application. */ - private static Preferences prefs = Preferences.userRoot().node(AppConstants.APP_NAME + "/prefs"); + private static final Preferences prefs = + Preferences.userRoot().node(AppConstants.APP_NAME + "/prefs"); /** Holds the render quality preference setting for the application. */ private static RenderQuality renderQuality; @@ -113,17 +102,11 @@ public class AppPreferences { */ private static final String KEY_ADD_ON_LOAD_DIR = "addOnLoadDir"; - /** Represents the key used to access the most recently used campaigns for the menu option. */ - private static final String KEY_MRU_CAMPAIGNS = "mruCampaigns"; - /** Represents the key used to load the most recent campaign on launch. Defaults to false */ private static final String KEY_LOAD_MRU_CAMPAIGN_AT_START = "loadMRUCampaignAtStart"; private static final boolean DEFAULT_LOAD_MRU_CAMPAIGN_AT_START = false; - /** Represents the key used to save the paint textures to the preferences. */ - private static final String KEY_SAVED_PAINT_TEXTURES = "savedTextures"; - /** * Represents the key used to determine if the user should be prompted to save the campaign on * quit. @@ -309,13 +292,6 @@ public class AppPreferences { private static final String ICON_THEME = "iconTheme"; private static final String DEFAULT_ICON_THEME = RessourceManager.ROD_TAKEHARA; - // When hill VBL was introduced, older versions of MapTool were unable to read the new topology - // modes. So we use a different preference key than in the past so older versions would not - // unexpectedly break. - private static final String KEY_TOPOLOGY_TYPES = "topologyTypes"; - private static final String KEY_OLD_TOPOLOGY_DRAWING_MODE = "topologyDrawingMode"; - private static final String DEFAULT_TOPOLOGY_TYPE = "VBL"; - private static final String KEY_WEB_END_POINT_PORT = "webEndPointPort"; private static final int DEFAULT_WEB_END_POINT = 654555; @@ -1407,125 +1383,6 @@ public static void setAddOnLoadDir(File file) { prefs.put(KEY_ADD_ON_LOAD_DIR, file.toString()); } - public static void addAssetRoot(File root) { - String list = prefs.get(KEY_ASSET_ROOTS, ""); - if (!list.isEmpty()) { - // Add the new one and then remove all duplicates. - list += ";" + root.getPath(); - String[] roots = list.split(";"); - StringBuilder result = new StringBuilder(list.length() + root.getPath().length() + 10); - Set rootList = new HashSet(roots.length); - - // This loop ensures that each path only appears once. If there are currently - // duplicates in the list, only the first one is kept. - for (String r : roots) { - if (!rootList.contains(r)) { - result.append(';').append(r); - rootList.add(r); - } - } - list = result.substring(1); - } else { - list += root.getPath(); - } - prefs.put(KEY_ASSET_ROOTS, list); - } - - public static Set getAssetRoots() { - String list = prefs.get(KEY_ASSET_ROOTS, ""); - String[] roots = list.split(";"); // FJE Probably should be File.path_separator ... - - Set rootList = new HashSet(); - for (String root : roots) { - File file = new File(root); - - // LATER: Should this actually remove it from the pref list ? - if (!file.exists()) { - continue; - } - rootList.add(file); - } - return rootList; - } - - public static void removeAssetRoot(File root) { - String list = prefs.get(KEY_ASSET_ROOTS, ""); - if (!list.isEmpty()) { - // Add the new one and then remove all duplicates. - String[] roots = list.split(";"); - StringBuilder result = new StringBuilder(list.length()); - Set rootList = new HashSet(roots.length); - String rootPath = root.getPath(); - - // This loop ensures that each path only appears once. If there are - // duplicates in the list, only the first one is kept. - for (String r : roots) { - if (!r.equals(rootPath) && !rootList.contains(r)) { - result.append(';').append(r); - rootList.add(r); - } - } - list = result.substring(result.length() > 0 ? 1 : 0); - prefs.put(KEY_ASSET_ROOTS, list); - } - } - - public static void setMruCampaigns(List mruCampaigns) { - StringBuilder combined = new StringBuilder(); - for (File file : mruCampaigns) { - String path = null; - try { - path = file.getCanonicalPath(); - } catch (IOException e) { - // Probably pretty rare, but we want to know about it - log.info("unexpected during file.getCanonicalPath()", e); // $NON-NLS-1$ - path = file.getPath(); - } - // It's important that '%3A' is done last. Note that the pathSeparator may not be a colon on - // the current platform, but it doesn't matter since it will be reconverted when read back in - // again. - // THink of the '%3A' as a symbol of the separator, not an encoding of the character. - combined.append(path.replaceAll("%", "%25").replaceAll(File.pathSeparator, "%3A")); - combined.append(File.pathSeparator); - } - prefs.put(KEY_MRU_CAMPAIGNS, combined.toString()); - } - - public static List getMruCampaigns() { - List mruCampaigns = new ArrayList(); - String combined = prefs.get(KEY_MRU_CAMPAIGNS, null); - if (combined != null) { - // It's important that '%3A' is done first - combined = combined.replaceAll("%3A", File.pathSeparator).replaceAll("%25", "%"); - String[] all = combined.split(File.pathSeparator); - for (String s : all) { - mruCampaigns.add(new File(s)); - } - } - return mruCampaigns; - } - - public static void setSavedPaintTextures(List savedTextures) { - StringBuilder combined = new StringBuilder(); - for (File savedTexture : savedTextures) { - combined.append(savedTexture.getPath()); - combined.append(File.pathSeparator); - } - prefs.put(KEY_SAVED_PAINT_TEXTURES, combined.toString()); - } - - public static List getSavedPaintTextures() { - List savedTextures = new ArrayList(); - String combined = prefs.get(KEY_SAVED_PAINT_TEXTURES, null); - if (combined != null) { - String[] all = combined.split(File.pathSeparator); - for (String s : all) { - savedTextures.add(new File(s)); - } - } - return savedTextures; - } - private static final String INIT_SHOW_TOKENS = "initShowTokens"; private static final boolean DEFAULT_INIT_SHOW_TOKENS = true; @@ -1655,27 +1512,6 @@ public static void setIconTheme(String theme) { prefs.put(ICON_THEME, theme); } - public static Zone.TopologyTypeSet getTopologyTypes() { - try { - String typeNames = prefs.get(KEY_TOPOLOGY_TYPES, ""); - if ("".equals(typeNames)) { - // Fallback to the key used prior to the introduction of various VBL types. - String oldDrawingMode = prefs.get(KEY_OLD_TOPOLOGY_DRAWING_MODE, DEFAULT_TOPOLOGY_TYPE); - return switch (oldDrawingMode) { - default -> new Zone.TopologyTypeSet(Zone.TopologyType.WALL_VBL); - case "VBL" -> new Zone.TopologyTypeSet(Zone.TopologyType.WALL_VBL); - case "MBL" -> new Zone.TopologyTypeSet(Zone.TopologyType.MBL); - case "COMBINED" -> new Zone.TopologyTypeSet( - Zone.TopologyType.WALL_VBL, Zone.TopologyType.MBL); - }; - } else { - return Zone.TopologyTypeSet.valueOf(typeNames); - } - } catch (Exception exc) { - return new Zone.TopologyTypeSet(Zone.TopologyType.WALL_VBL); - } - } - public static void setWebEndPointPort(int value) { prefs.putInt(KEY_WEB_END_POINT_PORT, value); } @@ -1702,19 +1538,6 @@ public String toString() { } } - /** - * Sets the topology mode preference. - * - * @param types the topology types. A value of null resets to default. - */ - public static void setTopologyTypes(Zone.TopologyTypeSet types) { - if (types == null) { - prefs.remove(KEY_TOPOLOGY_TYPES); - } else { - prefs.put(KEY_TOPOLOGY_TYPES, types.toString()); - } - } - /** * Returns the background color to use for NPC Map Labels. * diff --git a/src/main/java/net/rptools/maptool/client/AppSetup.java b/src/main/java/net/rptools/maptool/client/AppSetup.java index 3bbf22149e..289391663d 100644 --- a/src/main/java/net/rptools/maptool/client/AppSetup.java +++ b/src/main/java/net/rptools/maptool/client/AppSetup.java @@ -157,7 +157,7 @@ public static void installLibrary(String libraryName, URL resourceFile) throws I public static void installLibrary(final String libraryName, final File root) { // Add as a resource root - AppPreferences.addAssetRoot(root); + AppStatePersisted.addAssetRoot(root); if (MapTool.getFrame() != null) { MapTool.getFrame().addAssetRoot(root); diff --git a/src/main/java/net/rptools/maptool/client/AppStatePersisted.java b/src/main/java/net/rptools/maptool/client/AppStatePersisted.java new file mode 100644 index 0000000000..90efabd4f5 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/AppStatePersisted.java @@ -0,0 +1,224 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.prefs.Preferences; +import net.rptools.maptool.model.Zone; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Keeps track of application state between runs. + * + *

This started as an offshoot of {@link net.rptools.maptool.client.AppPreferences}, hence why + * the same node is used. But these keys do not represent preferences of the user, rather they are + * application state such as MRU lists the need to be remembered between runs. This also makes it + * different from {@link net.rptools.maptool.client.AppState} which is application state that is not + * preserved between runs. + */ +public class AppStatePersisted { + private static final Logger log = LogManager.getLogger(AppStatePersisted.class); + private static final Preferences prefs = + Preferences.userRoot().node(AppConstants.APP_NAME + "/prefs"); + + /** + * Defines the key constant for retrieving asset roots. + * + *

This constant is used to define the key for accessing asset roots in a configuration file or + * a data source. Asset roots represent the directories or paths where application assets are + * stored. + * + *

The asset roots can be used to locate and load files, images, or other resources required by + * the application at runtime. By convention, the asset root directories are organized in a + * structured manner to facilitate easy retrieval of assets. + */ + private static final String KEY_ASSET_ROOTS = "assetRoots"; + + /** Represents the key used to access the most recently used campaigns for the menu option. */ + private static final String KEY_MRU_CAMPAIGNS = "mruCampaigns"; + + // When hill VBL was introduced, older versions of MapTool were unable to read the new topology + // modes. So we use a different preference key than in the past so older versions would not + // unexpectedly break. + private static final String KEY_TOPOLOGY_TYPES = "topologyTypes"; + private static final String KEY_OLD_TOPOLOGY_DRAWING_MODE = "topologyDrawingMode"; + private static final String DEFAULT_TOPOLOGY_TYPE = "VBL"; + + /** Represents the key used to save the paint textures to the preferences. */ + private static final String KEY_SAVED_PAINT_TEXTURES = "savedTextures"; + + public static void clearAssetRoots() { + prefs.put(KEY_ASSET_ROOTS, ""); + } + + public static void addAssetRoot(File root) { + String list = prefs.get(KEY_ASSET_ROOTS, ""); + if (!list.isEmpty()) { + // Add the new one and then remove all duplicates. + list += ";" + root.getPath(); + String[] roots = list.split(";"); + var result = new StringBuilder(list.length() + root.getPath().length() + 10); + var rootList = new HashSet(roots.length); + + // This loop ensures that each path only appears once. If there are currently + // duplicates in the list, only the first one is kept. + for (String r : roots) { + if (!rootList.contains(r)) { + result.append(';').append(r); + rootList.add(r); + } + } + list = result.substring(1); + } else { + list += root.getPath(); + } + prefs.put(KEY_ASSET_ROOTS, list); + } + + public static Set getAssetRoots() { + String list = prefs.get(KEY_ASSET_ROOTS, ""); + String[] roots = list.split(";"); // FJE Probably should be File.path_separator ... + + var rootList = new HashSet(); + for (String root : roots) { + File file = new File(root); + + // LATER: Should this actually remove it from the pref list ? + if (!file.exists()) { + continue; + } + rootList.add(file); + } + return rootList; + } + + public static void removeAssetRoot(File root) { + String list = prefs.get(KEY_ASSET_ROOTS, ""); + if (!list.isEmpty()) { + // Add the new one and then remove all duplicates. + String[] roots = list.split(";"); + var result = new StringBuilder(list.length()); + var rootList = new HashSet(roots.length); + String rootPath = root.getPath(); + + // This loop ensures that each path only appears once. If there are + // duplicates in the list, only the first one is kept. + for (String r : roots) { + if (!r.equals(rootPath) && !rootList.contains(r)) { + result.append(';').append(r); + rootList.add(r); + } + } + list = result.substring(result.isEmpty() ? 0 : 1); + prefs.put(KEY_ASSET_ROOTS, list); + } + } + + public static void setMruCampaigns(List mruCampaigns) { + StringBuilder combined = new StringBuilder(); + for (File file : mruCampaigns) { + String path; + try { + path = file.getCanonicalPath(); + } catch (IOException e) { + // Probably pretty rare, but we want to know about it + log.info("unexpected during file.getCanonicalPath()", e); // $NON-NLS-1$ + path = file.getPath(); + } + // It's important that '%3A' is done last. Note that the pathSeparator may not be a colon on + // the current platform, but it doesn't matter since it will be reconverted when read back in + // again. + // THink of the '%3A' as a symbol of the separator, not an encoding of the character. + combined.append(path.replaceAll("%", "%25").replaceAll(File.pathSeparator, "%3A")); + combined.append(File.pathSeparator); + } + prefs.put(KEY_MRU_CAMPAIGNS, combined.toString()); + } + + public static List getMruCampaigns() { + var mruCampaigns = new ArrayList(); + String combined = prefs.get(KEY_MRU_CAMPAIGNS, null); + if (combined != null) { + // It's important that '%3A' is done first + combined = combined.replaceAll("%3A", File.pathSeparator).replaceAll("%25", "%"); + String[] all = combined.split(File.pathSeparator); + for (String s : all) { + mruCampaigns.add(new File(s)); + } + } + return mruCampaigns; + } + + public static Zone.TopologyTypeSet getTopologyTypes() { + try { + String typeNames = prefs.get(KEY_TOPOLOGY_TYPES, ""); + if ("".equals(typeNames)) { + // Fallback to the key used prior to the introduction of various VBL types. + String oldDrawingMode = prefs.get(KEY_OLD_TOPOLOGY_DRAWING_MODE, DEFAULT_TOPOLOGY_TYPE); + return switch (oldDrawingMode) { + case "VBL" -> new Zone.TopologyTypeSet(Zone.TopologyType.WALL_VBL); + case "MBL" -> new Zone.TopologyTypeSet(Zone.TopologyType.MBL); + case "COMBINED" -> new Zone.TopologyTypeSet( + Zone.TopologyType.WALL_VBL, Zone.TopologyType.MBL); + default -> new Zone.TopologyTypeSet(Zone.TopologyType.WALL_VBL); + }; + } else { + return Zone.TopologyTypeSet.valueOf(typeNames); + } + } catch (Exception exc) { + return new Zone.TopologyTypeSet(Zone.TopologyType.WALL_VBL); + } + } + + /** + * Sets the selected topology modes. + * + * @param types the topology types. A value of null resets to default. + */ + public static void setTopologyTypes(Zone.TopologyTypeSet types) { + if (types == null) { + prefs.remove(KEY_TOPOLOGY_TYPES); + } else { + prefs.put(KEY_TOPOLOGY_TYPES, types.toString()); + } + } + + public static void setSavedPaintTextures(List savedTextures) { + StringBuilder combined = new StringBuilder(); + for (File savedTexture : savedTextures) { + combined.append(savedTexture.getPath()); + combined.append(File.pathSeparator); + } + prefs.put(KEY_SAVED_PAINT_TEXTURES, combined.toString()); + } + + public static List getSavedPaintTextures() { + var savedTextures = new ArrayList(); + String combined = prefs.get(KEY_SAVED_PAINT_TEXTURES, null); + if (combined != null) { + String[] all = combined.split(File.pathSeparator); + for (String s : all) { + savedTextures.add(new File(s)); + } + } + return savedTextures; + } +} diff --git a/src/main/java/net/rptools/maptool/client/MRUCampaignManager.java b/src/main/java/net/rptools/maptool/client/MRUCampaignManager.java index 68428192b5..f7fc5661bd 100644 --- a/src/main/java/net/rptools/maptool/client/MRUCampaignManager.java +++ b/src/main/java/net/rptools/maptool/client/MRUCampaignManager.java @@ -105,11 +105,11 @@ private void addMRUsToMenu() { } private void saveMruCampaignList() { - AppPreferences.setMruCampaigns(mruCampaigns); + AppStatePersisted.setMruCampaigns(mruCampaigns); } private void loadMruCampaignList() { - mruCampaigns = AppPreferences.getMruCampaigns(); + mruCampaigns = AppStatePersisted.getMruCampaigns(); addMRUsToMenu(); } } diff --git a/src/main/java/net/rptools/maptool/client/MapTool.java b/src/main/java/net/rptools/maptool/client/MapTool.java index d01e8fe067..84a5326d42 100644 --- a/src/main/java/net/rptools/maptool/client/MapTool.java +++ b/src/main/java/net/rptools/maptool/client/MapTool.java @@ -674,7 +674,7 @@ private static void initialize() { Campaign cmpgn = CampaignFactory.createBasicCampaign(); // Set the Topology drawing mode to the last mode used for convenience // Should only be one zone, but let's cover our bases. - cmpgn.getZones().forEach(zone -> zone.setTopologyTypes(AppPreferences.getTopologyTypes())); + cmpgn.getZones().forEach(zone -> zone.setTopologyTypes(AppStatePersisted.getTopologyTypes())); // Stop the pre-init client/server. disconnect(); @@ -1278,7 +1278,7 @@ private static void postInitialize() { // alternately load MRU campaign if preference set else if (AppPreferences.getLoadMRUCampaignAtStart()) { try { - campaignFile = AppPreferences.getMruCampaigns().getFirst(); + campaignFile = AppStatePersisted.getMruCampaigns().getFirst(); if (campaignFile.exists()) { AppActions.loadCampaign(campaignFile); } diff --git a/src/main/java/net/rptools/maptool/client/swing/TopologyModeSelectionPanel.java b/src/main/java/net/rptools/maptool/client/swing/TopologyModeSelectionPanel.java index 527f95342a..8d777aacec 100644 --- a/src/main/java/net/rptools/maptool/client/swing/TopologyModeSelectionPanel.java +++ b/src/main/java/net/rptools/maptool/client/swing/TopologyModeSelectionPanel.java @@ -21,7 +21,7 @@ import javax.swing.JToolBar; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import net.rptools.maptool.client.AppPreferences; +import net.rptools.maptool.client.AppStatePersisted; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.ui.theme.Icons; import net.rptools.maptool.client.ui.theme.RessourceManager; @@ -49,7 +49,7 @@ public TopologyModeSelectionPanel() { modeButtons = new EnumMap<>(Zone.TopologyType.class); - var initiallySelectedTypes = AppPreferences.getTopologyTypes(); + var initiallySelectedTypes = AppStatePersisted.getTopologyTypes(); createAndAddModeButton( Zone.TopologyType.WALL_VBL, Icons.TOOLBAR_TOPOLOGY_TYPE_VBL_ON, @@ -120,9 +120,9 @@ public void stateChanged(ChangeEvent e) { } public void setMode(Zone.TopologyTypeSet topologyTypes) { - AppPreferences.setTopologyTypes(topologyTypes); + AppStatePersisted.setTopologyTypes(topologyTypes); if (topologyTypes == null) { - topologyTypes = AppPreferences.getTopologyTypes(); + topologyTypes = AppStatePersisted.getTopologyTypes(); } for (final var entry : modeButtons.entrySet()) { diff --git a/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java b/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java index 2680fca2e0..607c125718 100644 --- a/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java +++ b/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java @@ -1131,7 +1131,7 @@ public ConnectionStatusPanel getConnectionStatusPanel() { } private void restorePreferences() { - Set assetRootList = AppPreferences.getAssetRoots(); + Set assetRootList = AppStatePersisted.getAssetRoots(); for (File file : assetRootList) { addAssetRoot(file); } diff --git a/src/main/java/net/rptools/maptool/client/ui/addresource/AddResourceDialog.java b/src/main/java/net/rptools/maptool/client/ui/addresource/AddResourceDialog.java index 5e385354e9..8f0922d19d 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addresource/AddResourceDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/addresource/AddResourceDialog.java @@ -40,8 +40,8 @@ import javax.swing.ListSelectionModel; import javax.swing.SwingWorker; import net.rptools.lib.FileUtil; -import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.client.AppSetup; +import net.rptools.maptool.client.AppStatePersisted; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.RemoteFileDownloader; import net.rptools.maptool.client.WebDownloader; @@ -196,7 +196,7 @@ private void downloadLibraryList() { new DownloadListWorker( getLibraryList(), new WebDownloader(new URL(LIBRARY_LIST_URL)), - AppPreferences.getAssetRoots()); + AppStatePersisted.getAssetRoots()); worker.execute(); } catch (MalformedURLException e) { MapTool.showMessage( diff --git a/src/main/java/net/rptools/maptool/client/ui/mappropertiesdialog/MapPropertiesDialog.java b/src/main/java/net/rptools/maptool/client/ui/mappropertiesdialog/MapPropertiesDialog.java index b67c2d70da..786acb4f48 100644 --- a/src/main/java/net/rptools/maptool/client/ui/mappropertiesdialog/MapPropertiesDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/mappropertiesdialog/MapPropertiesDialog.java @@ -185,7 +185,7 @@ public void actionPerformed(ActionEvent e) { // Color picker paintChooser = new PaintChooser(); AssetPanelModel model = new AssetPanelModel(); - Set assetRootList = AppPreferences.getAssetRoots(); + Set assetRootList = AppStatePersisted.getAssetRoots(); for (File file : assetRootList) { model.addRootGroup(new AssetDirectory(file, AppConstants.IMAGE_FILE_FILTER)); } @@ -669,7 +669,7 @@ public Asset chooseAsset() { private JComponent createImageExplorerPanel() { AssetPanelModel model = new AssetPanelModel(); - Set assetRootList = AppPreferences.getAssetRoots(); + Set assetRootList = AppStatePersisted.getAssetRoots(); for (File file : assetRootList) { model.addRootGroup(new AssetDirectory(file, AppConstants.IMAGE_FILE_FILTER)); } diff --git a/src/main/java/net/rptools/maptool/model/Zone.java b/src/main/java/net/rptools/maptool/model/Zone.java index e4c771bb28..d5acf2dea0 100644 --- a/src/main/java/net/rptools/maptool/model/Zone.java +++ b/src/main/java/net/rptools/maptool/model/Zone.java @@ -25,7 +25,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import net.rptools.lib.MD5Key; -import net.rptools.maptool.client.AppPreferences; +import net.rptools.maptool.client.AppStatePersisted; import net.rptools.maptool.client.AppUtil; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.tool.drawing.UndoPerZone; @@ -1376,7 +1376,7 @@ public void setAStarRounding(AStarRoundingOptions aStarRounding) { public TopologyTypeSet getTopologyTypes() { if (topologyTypes == null) { - topologyTypes = AppPreferences.getTopologyTypes(); + topologyTypes = AppStatePersisted.getTopologyTypes(); } return topologyTypes; From a2f7265c37e7a94d2d55231c2fd9f268524168a0 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Mon, 7 Oct 2024 19:42:25 -0700 Subject: [PATCH 052/146] Define new `Preference<>` type All `AppPreferences` preferences will be made instances of this type in the next change. For now, we are just adding the new classes: - `Type` defines how to store the preference. - `Preference` defines the preference itself by its key, type, default value, and validation. - Implementations of `Preference` specialize the storage characteristics, i.e., which methods on the `Preference` object to use when loading and storing a value. --- .../maptool/client/AppPreferences.java | 270 ++++++++++++++++++ 1 file changed, 270 insertions(+) diff --git a/src/main/java/net/rptools/maptool/client/AppPreferences.java b/src/main/java/net/rptools/maptool/client/AppPreferences.java index 50609aec8a..4c6c48bc7e 100644 --- a/src/main/java/net/rptools/maptool/client/AppPreferences.java +++ b/src/main/java/net/rptools/maptool/client/AppPreferences.java @@ -19,7 +19,13 @@ import java.awt.Graphics2D; import java.awt.RenderingHints; import java.io.File; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.prefs.Preferences; +import javax.annotation.Nullable; import net.rptools.maptool.client.ui.theme.RessourceManager; import net.rptools.maptool.client.walker.WalkerMetric; import net.rptools.maptool.language.I18N; @@ -1787,4 +1793,268 @@ public static boolean getShowMapLabelBorder() { public static void setShowMapLabelBorder(boolean show) { prefs.putBoolean(KEY_MAP_LABEL_SHOW_BORDER, show); } + + private interface Type { + void set(Preferences node, String key, T value); + + T get(Preferences node, String key, Supplier defaultValue); + } + + public static final class Preference { + private final String key; + private final Supplier defaultValue; + private final Type type; + + private Predicate validator = value -> true; + private boolean cachingEnabled = false; + private @Nullable T cachedValue; + + private final List> onChangeHandlers = new CopyOnWriteArrayList<>(); + + private Preference(String key, T defaultValue, Type type) { + this.key = key; + this.defaultValue = () -> defaultValue; + this.type = type; + } + + private Preference(String key, Supplier defaultValue, Type type) { + this.key = key; + this.defaultValue = defaultValue; + this.type = type; + } + + public String name() { + return key; + } + + /** + * Loads and validates the value of the preference. + * + *

If validation is unsuccessful, clears the preference and returns it the default value. + * + * @return The value of the preference. + */ + public T get() { + if (cachingEnabled && cachedValue != null) { + return cachedValue; + } + + var value = type.get(prefs, key, defaultValue); + if (!validator.test(value)) { + log.warn("Value read from preference {} did not pass validation: {}", name(), value); + value = getDefault(); + remove(); + } + + cachedValue = value; + return value; + } + + /** + * Validates and stores the value of the preference. + * + *

If validation is unsuccessful, stores the default value instead. + * + * @param value The value to set. + */ + public void set(T value) { + if (!validator.test(value)) { + log.warn("Value written to preference {} did not pass validation: {}", name(), value); + value = getDefault(); + } + + type.set(prefs, key, value); + cachedValue = value; + + for (var handler : onChangeHandlers) { + handler.accept(value); + } + } + + public void remove() { + prefs.remove(key); + cachedValue = getDefault(); + + for (var handler : onChangeHandlers) { + handler.accept(cachedValue); + } + } + + public T getDefault() { + return defaultValue.get(); + } + + public Preference cacheIt() { + this.cachingEnabled = true; + return this; + } + + public Preference validateIt(Predicate predicate) { + validator = predicate; + return this; + } + + public void onChange(Consumer handler) { + onChangeHandlers.add(handler); + } + } + + private static final class BooleanType implements Type { + public static Preference create(String key, boolean defaultValue) { + return new Preference<>(key, defaultValue, new BooleanType()); + } + + @Override + public void set(Preferences prefs, String key, Boolean value) { + prefs.putBoolean(key, value); + } + + @Override + public Boolean get(Preferences prefs, String key, Supplier defaultValue) { + return prefs.getBoolean(key, defaultValue.get()); + } + } + + private static final class IntegerType implements Type { + public static Preference create(String key, int defaultValue) { + return new Preference<>(key, defaultValue, new IntegerType()); + } + + @Override + public void set(Preferences prefs, String key, Integer value) { + prefs.putInt(key, value); + } + + @Override + public Integer get(Preferences prefs, String key, Supplier defaultValue) { + return prefs.getInt(key, defaultValue.get()); + } + } + + private static final class ByteType implements Type { + public static Preference create(String key, int defaultValue) { + return new Preference<>(key, defaultValue, new ByteType()); + } + + @Override + public void set(Preferences prefs, String key, Integer value) { + prefs.putInt(key, range0to255(value)); + } + + @Override + public Integer get(Preferences prefs, String key, Supplier defaultValue) { + return range0to255(prefs.getInt(key, defaultValue.get())); + } + + private static int range0to255(int value) { + return Math.clamp(value, 0, 255); + } + } + + private static final class DoubleType implements Type { + public static Preference create(String key, double defaultValue) { + return new Preference<>(key, defaultValue, new DoubleType()); + } + + @Override + public void set(Preferences prefs, String key, Double value) { + prefs.putDouble(key, value); + } + + @Override + public Double get(Preferences prefs, String key, Supplier defaultValue) { + return prefs.getDouble(key, defaultValue.get()); + } + } + + private static final class StringType implements Type { + public static Preference create(String key, String defaultValue) { + return new Preference<>(key, defaultValue, new StringType()); + } + + @Override + public void set(Preferences prefs, String key, String value) { + prefs.put(key, value); + } + + @Override + public String get(Preferences prefs, String key, Supplier defaultValue) { + return prefs.get(key, defaultValue.get()); + } + } + + private static final class FileType implements Type { + public static Preference create(String key, Supplier defaultValue) { + return new Preference<>(key, defaultValue, new FileType()); + } + + @Override + public void set(Preferences prefs, String key, File value) { + prefs.put(key, value.toString()); + } + + @Override + public File get(Preferences prefs, String key, Supplier defaultValue) { + String filePath = prefs.get(key, null); + if (filePath != null) { + return new File(filePath); + } + + return defaultValue.get(); + } + } + + private static final class EnumType> implements Type { + public static > Preference create( + Class class_, String key, T defaultValue) { + return new Preference<>(key, defaultValue, new EnumType<>(class_)); + } + + private final Class class_; + + public EnumType(Class class_) { + this.class_ = class_; + } + + @Override + public void set(Preferences prefs, String key, T value) { + prefs.put(key, value.name()); + } + + @Override + public T get(Preferences prefs, String key, Supplier defaultValue) { + var stored = prefs.get(key, null); + if (stored == null) { + return defaultValue.get(); + } + + try { + return Enum.valueOf(class_, stored); + } catch (Exception e) { + return defaultValue.get(); + } + } + } + + private static final class ColorType implements Type { + public static Preference create(String key, Color defaultValue, boolean hasAlpha) { + return new Preference<>(key, defaultValue, new ColorType(hasAlpha)); + } + + private final boolean hasAlpha; + + public ColorType(boolean hasAlpha) { + this.hasAlpha = hasAlpha; + } + + @Override + public void set(Preferences prefs, String key, Color value) { + prefs.putInt(key, value.getRGB()); + } + + @Override + public Color get(Preferences prefs, String key, Supplier defaultValue) { + return new Color(prefs.getInt(key, defaultValue.get().getRGB()), hasAlpha); + } + } } From 110e1e6bee9a03b425176dc541f3c122e143153f Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Mon, 7 Oct 2024 19:44:23 -0700 Subject: [PATCH 053/146] Convert all AppPreference preferences to new type All preferences are now instances of `Preference`. The few types that had additional update logic not use the change listener to notify observers about it. In order to really make this change effective, we no longer store any colors component-wise since they can easily be stored as a single integer. For those preferences that once had component-wise storage, the static initializer ensures backwards compatibility by detecting whether a new value is present and loading the old values if not. --- .../java/net/rptools/lib/image/ImageUtil.java | 4 +- .../rptools/lib/image/ThumbnailManager.java | 2 +- .../rptools/maptool/client/AppActions.java | 8 +- .../maptool/client/AppPreferences.java | 1826 +++-------------- .../net/rptools/maptool/client/AppState.java | 4 +- .../net/rptools/maptool/client/AppUpdate.java | 12 +- .../maptool/client/AutoSaveManager.java | 2 +- .../rptools/maptool/client/ChatAutoSave.java | 32 +- .../maptool/client/ClientMessageHandler.java | 2 +- .../net/rptools/maptool/client/MapTool.java | 30 +- .../rptools/maptool/client/MapToolUtil.java | 11 +- .../client/functions/ExportDataFunctions.java | 2 +- .../client/functions/IsTrustedFunction.java | 2 +- .../client/functions/MapFunctions.java | 8 +- .../client/functions/RESTfulFunctions.java | 4 +- .../client/functions/SoundFunctions.java | 4 +- .../functions/TokenLocationFunctions.java | 2 +- .../client/functions/TokenMoveFunctions.java | 2 +- .../client/functions/getInfoFunction.java | 20 +- .../javascript/api/JSAPIClientInfo.java | 8 +- .../maptool/client/swing/ImagePanel.java | 4 +- .../maptool/client/swing/TooltipView.java | 2 +- .../swing/label/FlatImageLabelFactory.java | 26 +- .../rptools/maptool/client/tool/AI_Tool.java | 4 +- .../maptool/client/tool/AI_UseVblTool.java | 6 +- .../maptool/client/tool/DefaultTool.java | 4 +- .../maptool/client/tool/FacingTool.java | 5 +- .../maptool/client/tool/PointerTool.java | 29 +- .../maptool/client/tool/StampTool.java | 2 +- .../client/ui/AbstractTokenPopupMenu.java | 2 +- .../maptool/client/ui/AssetViewerDialog.java | 2 +- .../client/ui/ChatTypingNotification.java | 4 +- .../maptool/client/ui/MapToolFrame.java | 45 +- .../client/ui/PreviewPanelFileChooser.java | 2 +- .../maptool/client/ui/ZoneSelectionPopup.java | 2 +- .../TokenBarController.java | 4 +- .../TokenStatesController.java | 2 +- .../chat/SmileyChatTranslationRuleGroup.java | 2 +- .../client/ui/commandpanel/CommandPanel.java | 10 +- .../client/ui/commandpanel/MessagePanel.java | 4 +- .../ui/htmlframe/HTMLOverlayManager.java | 2 +- .../maptool/client/ui/htmlframe/HTMLPane.java | 2 +- .../ui/htmlframe/HTMLWebViewManager.java | 2 +- .../buttons/MacroButtonPrefs.java | 4 +- .../dialog/MacroEditorDialog.java | 2 +- .../MapPropertiesDialog.java | 16 +- .../preferencesdialog/PreferencesDialog.java | 403 ++-- .../ui/sheet/stats/StatSheetListener.java | 4 +- .../startserverdialog/StartServerDialog.java | 2 +- .../StartServerDialogPreferences.java | 2 +- .../client/ui/theme/RessourceManager.java | 2 +- .../token/dialog/create/NewTokenDialog.java | 2 +- .../ui/token/dialog/edit/EditTokenDialog.java | 10 +- .../InitiativeListCellRenderer.java | 2 +- .../client/ui/tokenpanel/InitiativePanel.java | 19 +- .../client/ui/zone/renderer/FogRenderer.java | 2 +- .../client/ui/zone/renderer/HaloRenderer.java | 2 +- .../ui/zone/renderer/LightsRenderer.java | 4 +- .../ui/zone/renderer/LumensRenderer.java | 4 +- .../zone/renderer/VisionOverlayRenderer.java | 4 +- .../client/ui/zone/renderer/ZoneRenderer.java | 38 +- .../maptool/model/CampaignProperties.java | 5 +- .../java/net/rptools/maptool/model/Grid.java | 4 +- .../rptools/maptool/model/HeroLabData.java | 8 +- .../rptools/maptool/model/InitiativeList.java | 2 +- .../rptools/maptool/model/IsometricGrid.java | 2 +- .../maptool/model/MacroButtonProperties.java | 12 +- .../net/rptools/maptool/model/SquareGrid.java | 2 +- .../rptools/maptool/model/ZoneFactory.java | 16 +- .../maptool/model/player/LocalPlayer.java | 2 +- .../model/sheet/stats/StatSheetContext.java | 4 +- .../rptools/maptool/server/ServerPolicy.java | 10 +- .../rptools/maptool/util/ExtractHeroLab.java | 2 +- .../net/rptools/maptool/util/MessageUtil.java | 2 +- .../rptools/maptool/util/PersistenceUtil.java | 4 +- .../net/rptools/maptool/util/UPnPUtil.java | 2 +- 76 files changed, 742 insertions(+), 2009 deletions(-) diff --git a/src/main/java/net/rptools/lib/image/ImageUtil.java b/src/main/java/net/rptools/lib/image/ImageUtil.java index fe4195ed5b..afce6c607d 100644 --- a/src/main/java/net/rptools/lib/image/ImageUtil.java +++ b/src/main/java/net/rptools/lib/image/ImageUtil.java @@ -155,7 +155,7 @@ public static BufferedImage createCompatibleImage( Graphics2D g = null; try { g = compImg.createGraphics(); - AppPreferences.getRenderQuality().setRenderingHints(g); + AppPreferences.renderQuality.get().setRenderingHints(g); g.drawImage(img, 0, 0, width, height, null); } finally { if (g != null) { @@ -442,7 +442,7 @@ public static ImageIcon scaleImage(ImageIcon icon, int w, int h) { */ public static BufferedImage scaleBufferedImage(BufferedImage image, int width, int height) { ResampleOp resampleOp = - new ResampleOp(width, height, AppPreferences.getRenderQuality().getResampleOpFilter()); + new ResampleOp(width, height, AppPreferences.renderQuality.get().getResampleOpFilter()); return resampleOp.filter(image, null); } } diff --git a/src/main/java/net/rptools/lib/image/ThumbnailManager.java b/src/main/java/net/rptools/lib/image/ThumbnailManager.java index e57db4cc4b..3067b916a6 100644 --- a/src/main/java/net/rptools/lib/image/ThumbnailManager.java +++ b/src/main/java/net/rptools/lib/image/ThumbnailManager.java @@ -79,7 +79,7 @@ private Image createThumbnail(File file) throws IOException { new BufferedImage(imgSize.width, imgSize.height, ImageUtil.pickBestTransparency(image)); Graphics2D g = thumbnailImage.createGraphics(); - AppPreferences.getRenderQuality().setShrinkRenderingHints(g); + AppPreferences.renderQuality.get().setShrinkRenderingHints(g); g.drawImage(image, 0, 0, imgSize.width, imgSize.height, null); g.dispose(); diff --git a/src/main/java/net/rptools/maptool/client/AppActions.java b/src/main/java/net/rptools/maptool/client/AppActions.java index c669f699bc..afd87649f7 100644 --- a/src/main/java/net/rptools/maptool/client/AppActions.java +++ b/src/main/java/net/rptools/maptool/client/AppActions.java @@ -2600,7 +2600,7 @@ protected void done() { ImageManager.flush(); // Clear out the old campaign's images AppState.setCampaignFile(campaignFile); - AppPreferences.setLoadDir(campaignFile.getParentFile()); + AppPreferences.loadDirectory.set(campaignFile.getParentFile()); AppMenuBar.getMruManager().addMRUCampaign(campaignFile); campaign.campaign.setName(AppState.getCampaignName()); // Update campaign name @@ -2792,7 +2792,7 @@ private static void saveAndUpdateCampaignName(File selectedFile, Runnable onSucc } doSaveCampaign(campaignFile, onSuccess); AppState.setCampaignFile(campaignFile); - AppPreferences.setSaveDir(campaignFile.getParentFile()); + AppPreferences.saveDirectory.set(campaignFile.getParentFile()); AppMenuBar.getMruManager().addMRUCampaign(AppState.getCampaignFile()); if (MapTool.isHostingServer() || MapTool.isPersonalServer()) { MapTool.serverCommand().setCampaignName(AppState.getCampaignName()); @@ -2846,7 +2846,7 @@ protected void executeAction() { } } PersistenceUtil.saveMap(zr.getZone(), mapFile); - AppPreferences.setSaveMapDir(mapFile.getParentFile()); + AppPreferences.mapSaveDirectory.set(mapFile.getParentFile()); MapTool.showInformation("msg.info.mapSaved"); } catch (IOException ioe) { MapTool.showError("msg.error.failedSaveMap", ioe); @@ -2973,7 +2973,7 @@ protected void done() { try { PersistedMap map = get(); - AppPreferences.setLoadDir(mapFile.getParentFile()); + AppPreferences.loadDirectory.set(mapFile.getParentFile()); if ((map.zone.getExposedArea() != null && !map.zone.getExposedArea().isEmpty()) || (map.zone.getExposedAreaMetaData() != null && !map.zone.getExposedAreaMetaData().isEmpty())) { diff --git a/src/main/java/net/rptools/maptool/client/AppPreferences.java b/src/main/java/net/rptools/maptool/client/AppPreferences.java index 4c6c48bc7e..ff458fbe5b 100644 --- a/src/main/java/net/rptools/maptool/client/AppPreferences.java +++ b/src/main/java/net/rptools/maptool/client/AppPreferences.java @@ -26,12 +26,10 @@ import java.util.function.Supplier; import java.util.prefs.Preferences; import javax.annotation.Nullable; -import net.rptools.maptool.client.ui.theme.RessourceManager; import net.rptools.maptool.client.walker.WalkerMetric; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.GridFactory; import net.rptools.maptool.model.Label; -import net.rptools.maptool.model.Token; import net.rptools.maptool.model.Zone; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -53,696 +51,376 @@ public class AppPreferences { private static final Preferences prefs = Preferences.userRoot().node(AppConstants.APP_NAME + "/prefs"); - /** Holds the render quality preference setting for the application. */ - private static RenderQuality renderQuality; + public static final Preference fillSelectionBox = + BooleanType.create("fillSelectionBox", true); - /** - * Defines the key constant for retrieving asset roots. - * - *

This constant is used to define the key for accessing asset roots in a configuration file or - * a data source. Asset roots represent the directories or paths where application assets are - * stored. - * - *

The asset roots can be used to locate and load files, images, or other resources required by - * the application at runtime. By convention, the asset root directories are organized in a - * structured manner to facilitate easy retrieval of assets. - */ - private static final String KEY_ASSET_ROOTS = "assetRoots"; - - /** - * The constant representing the key for the save directory. - * - *

This key is used to access and store the value of the save directory. The value associated - * with this key should be a string representing the directory path. - */ - private static final String KEY_SAVE_DIR = "saveDir"; - - /** - * The constant representing the key for saving token directory. This constant is used to retrieve - * the directory path where token information will be exported. - */ - private static final String KEY_SAVE_TOKEN_DIR = "saveTokenDir"; - - /** - * The variable to store the key for saving the map directory. - * - *

This variable is used to configure the directory where the map will be export. - */ - private static final String KEY_SAVE_MAP_DIR = "saveMapDir"; - - /** - * The key for the load directory. - * - *

This constant represents the key used to specify the last directory used for loading files - * so that subsequent dialogs will be opened with the same path. - * - *

The value should be a String representing the directory path. - */ - private static final String KEY_LOAD_DIR = "loadDir"; - - /** - * The configuration key for specifying the directory path where the last add-on was loaded from, - * so that subsequent dialogs will be opened with the same path. - * - *

The value should be a String representing the directory path. - */ - private static final String KEY_ADD_ON_LOAD_DIR = "addOnLoadDir"; - - /** Represents the key used to load the most recent campaign on launch. Defaults to false */ - private static final String KEY_LOAD_MRU_CAMPAIGN_AT_START = "loadMRUCampaignAtStart"; - - private static final boolean DEFAULT_LOAD_MRU_CAMPAIGN_AT_START = false; - - /** - * Represents the key used to determine if the user should be prompted to save the campaign on - * quit. - */ - private static final String KEY_SAVE_REMINDER = "autoSaveReminder"; - - /** - * The default value for the {@code KEY_SAVE_REMINDER} key. - * - * @see #KEY_SAVE_REMINDER - */ - private static final boolean DEFAULT_SAVE_REMINDER = true; - - /** - * Represents the key for the method used to determine which name of the token (Player/GM) the - * number is appended to. - */ - private static final String KEY_TOKEN_NUMBER_DISPLAY = "tokenNumberDisplayg"; - - /** - * The default value for the {@code KEY_TOKEN_NUMBER_DISPLAY} preference option,. - * - * @see #KEY_TOKEN_NUMBER_DISPLAY - */ - private static final String DEFAULT_TOKEN_NUMBER_DISPLAY = Token.NUM_ON_NAME; - - /** Represents the key used to retrieve the number of minutes between auto saves. */ - private static final String KEY_AUTO_SAVE_INCREMENT = "autoSaveIncrement"; - - /** - * The default value for the {@code KEY_AUTO_SAVE_INCREMENT} preference option. - * - * @see #KEY_AUTO_SAVE_INCREMENT - */ - private static final int DEFAULT_AUTO_SAVE_INCREMENT = 5; // Minutes - - private static final String KEY_CHAT_AUTOSAVE_TIME = "chatAutosaveTime"; - private static final int DEFAULT_CHAT_AUTOSAVE_TIME = 0; // Minutes; zero=disabled - - private static final String KEY_CHAT_FILENAME_FORMAT = "chatFilenameFormat"; - private static final String DEFAULT_CHAT_FILENAME_FORMAT = - "chatlog-%1$tF-%1$tR.html"; // http://download.oracle.com/javase/1.5.0/docs/api/java/util/Formatter.html - - private static final String KEY_DUPLICATE_TOKEN_NUMBER = "duplicateTokenNumber"; - private static final String DEFAULT_DUPLICATE_TOKEN_NUMBER = Token.NUM_INCREMENT; - - private static final String KEY_NEW_TOKEN_NAMING = "newTokenNaming"; - private static final String DEFAULT_NEW_TOKEN_NAMING = Token.NAME_USE_FILENAME; - - private static final String KEY_USE_HALO_COLOR_ON_VISION_OVERLAY = "useHaloColorForVisionOverlay"; - private static final boolean DEFAULT_USE_HALO_COLOR_ON_VISION_OVERLAY = false; - - private static final String KEY_HALO_OVERLAY_OPACITY = "haloOverlayOpacity"; - private static final int DEFAULT_HALO_OVERLAY_OPACITY = 60; - - private static final String KEY_AURA_OVERLAY_OPACITY = "auraOverlayOpacity"; - private static final int DEFAULT_AURA_OVERLAY_OPACITY = 60; - - private static final String KEY_LIGHT_OVERLAY_OPACITY = "lightOverlayOpacity"; - private static final int DEFAULT_LIGHT_OVERLAY_OPACITY = 60; - - private static final String KEY_LUMENS_OVERLAY_OPACITY = "lumensOverlayOpacity"; - private static final int DEFAULT_LUMENS_OVERLAY_OPACITY = 120; - - private static final String KEY_LUMENS_OVERLAY_BORDER_THICKNESS = "lumensOverlayBorderThickness"; - private static final int DEFAULT_LUMENS_OVERLAY_BORDER_THICKNESS = 5; - - private static final String KEY_LUMENS_OVERLAY_SHOW_BY_DEFAULT = "lumensOverlayShowByDefault"; - private static final boolean DEFAULT_LUMENS_OVERLAY_SHOW_BY_DEFAULT = false; - - private static final String KEY_LIGHTS_SHOW_BY_DEFAULT = "lightsShowByDefault"; - private static final boolean DEFAULT_LIGHTS_SHOW_BY_DEFAULT = true; - - private static final String KEY_FOG_OVERLAY_OPACITY = "fogOverlayOpacity"; - private static final int DEFAULT_FOG_OVERLAY_OPACITY = 100; - - private static final String KEY_HALO_LINE_WIDTH = "haloLineWidth"; - private static final int DEFAULT_HALO_LINE_WIDTH = 2; - - private static final String KEY_AUTO_REVEAL_VISION_ON_GM_MOVEMENT = "autoRevealVisionOnGMMove"; - private static final boolean DEFAULT_AUTO_REVEAL_VISION_ON_GM_MOVEMENT = false; - - private static final String KEY_USE_SOFT_FOG_EDGES = "useSoftFog"; - private static final boolean DEFAULT_USE_SOFT_FOG_EDGES = true; - - private static final String KEY_MAP_VISIBILITY_WARNING = "mapVisibilityWarning"; - - private static final String KEY_NEW_MAPS_HAVE_FOW = "newMapsHaveFow"; - private static final boolean DEFAULT_NEW_MAPS_HAVE_FOW = false; - - private static final String KEY_NEW_TOKENS_VISIBLE = "newTokensVisible"; - private static final boolean DEFAULT_NEW_TOKENS_VISIBLE = true; + public static final Preference chatColor = + ColorType.create("chatColor", Color.black, false); - private static final String KEY_NEW_MAPS_VISIBLE = "newMapsVisible"; - private static final boolean DEFAULT_NEW_MAPS_VISIBLE = true; + public static final Preference saveReminder = + BooleanType.create("autoSaveReminder", true); - private static final String KEY_NEW_OBJECTS_VISIBLE = "newObjectsVisible"; - private static final boolean DEFAULT_NEW_OBJECTS_VISIBLE = true; + public static final Preference autoSaveIncrement = + IntegerType.create("autoSaveIncrement", 5); - private static final String KEY_NEW_BACKGROUNDS_VISIBLE = "newBackgroundsVisible"; - private static final boolean DEFAULT_NEW_BACKGROUNDS_VISIBLE = true; + public static final Preference chatAutoSaveTimeInMinutes = + IntegerType.create("chatAutosaveTime", 0); - private static final String KEY_TOKENS_START_FREESIZE = "newTokensStartFreesize"; - private static final boolean DEFAULT_TOKENS_START_FREESIZE = false; + public static final Preference chatFilenameFormat = + StringType.create("chatFilenameFormat", "chatlog-%1$tF-%1$tR.html"); - private static final String KEY_TOKENS_WARN_WHEN_DELETED = "tokensWarnWhenDeleted"; - private static final boolean DEFAULT_TOKENS_WARN_WHEN_DELETED = true; + public static final Preference tokenNumberDisplay = + StringType.create("tokenNumberDisplayg", "Name"); - private static final String KEY_DRAW_WARN_WHEN_DELETED = "drawWarnWhenDeleted"; - private static final boolean DEFAULT_DRAW_WARN_WHEN_DELETED = true; + public static final Preference duplicateTokenNumber = + StringType.create("duplicateTokenNumber", "Increment"); - private static final String KEY_TOKENS_START_SNAP_TO_GRID = "newTokensStartSnapToGrid"; - private static final boolean DEFAULT_TOKENS_START_SNAP_TO_GRID = true; + public static final Preference newTokenNaming = + StringType.create("newTokenNaming", "Use Filename"); - private static final String KEY_TOKENS_SNAP_WHILE_DRAGGING = "tokensSnapWhileDragging"; - private static final boolean DEFAULT_KEY_TOKENS_SNAP_WHILE_DRAGGING = true; + public static final Preference useHaloColorOnVisionOverlay = + BooleanType.create("useHaloColorForVisionOverlay", false); - private static final String KEY_HIDE_MOUSE_POINTER_WHILE_DRAGGING = - "hideMousePointerWhileDragging"; - private static final boolean DEFAULT_KEY_HIDE_MOUSE_POINTER_WHILE_DRAGGING = true; + public static final Preference mapVisibilityWarning = + BooleanType.create("mapVisibilityWarning", false); - private static final String KEY_HIDE_TOKEN_STACK_INDICATOR = "hideTokenStackIndicator"; - private static final boolean DEFAULT_KEY_HIDE_TOKEN_STACK_INDICATOR = false; + public static final Preference autoRevealVisionOnGMMovement = + BooleanType.create("autoRevealVisionOnGMMove", false); - private static final String KEY_OBJECTS_START_SNAP_TO_GRID = "newStampsStartSnapToGrid"; - private static final boolean DEFAULT_OBJECTS_START_SNAP_TO_GRID = false; + public static final Preference haloOverlayOpacity = + ByteType.create("haloOverlayOpacity", 60); - private static final String KEY_OBJECTS_START_FREESIZE = "newStampsStartFreesize"; - private static final boolean DEFAULT_OBJECTS_START_FREESIZE = true; + public static final Preference auraOverlayOpacity = + ByteType.create("auraOverlayOpacity", 60); - private static final String KEY_BACKGROUNDS_START_SNAP_TO_GRID = "newBackgroundsStartSnapToGrid"; - private static final boolean DEFAULT_BACKGROUNDS_START_SNAP_TO_GRID = false; + public static final Preference lightOverlayOpacity = + ByteType.create("lightOverlayOpacity", 60); - private static final String KEY_BACKGROUNDS_START_FREESIZE = "newBackgroundsStartFreesize"; - private static final boolean DEFAULT_BACKGROUNDS_START_FREESIZE = true; + public static final Preference lumensOverlayOpacity = + ByteType.create("lumensOverlayOpacity", 120); - private static final String KEY_SOUNDS_ONLY_WHEN_NOT_FOCUSED = - "playSystemSoundsOnlyWhenNotFocused"; - private static final boolean DEFAULT_SOUNDS_ONLY_WHEN_NOT_FOCUSED = false; + public static final Preference fogOverlayOpacity = + ByteType.create("fogOverlayOpacity", 100); - private static final String KEY_SYRINSCAPE_ACTIVE = "syrinscapeActive"; - private static final boolean DEFAULT_SYRINSCAPE_ACTIVE = false; + public static final Preference lumensOverlayBorderThickness = + IntegerType.create("lumensOverlayBorderThickness", 5); - private static final String KEY_SHOW_AVATAR_IN_CHAT = "showAvatarInChat"; - private static final boolean DEFAULT_SHOW_AVATAR_IN_CHAT = true; + public static final Preference lumensOverlayShowByDefault = + BooleanType.create("lumensOverlayShowByDefault", false); - private static final String KEY_SHOW_DIALOG_ON_NEW_TOKEN = "showDialogOnNewToken"; - private static final boolean DEFAULT_SHOW_DIALOG_ON_NEW_TOKEN = true; + public static final Preference lightsShowByDefault = + BooleanType.create("lightsShowByDefault", true); - private static final String KEY_INSERT_SMILIES = "insertSmilies"; - private static final boolean DEFAULT_SHOW_SMILIES = true; + public static final Preference haloLineWidth = IntegerType.create("haloLineWidth", 2); - private static final String KEY_MOVEMENT_METRIC = "movementMetric"; - private static final WalkerMetric DEFAULT_MOVEMENT_METRIC = WalkerMetric.ONE_TWO_ONE; + public static final Preference typingNotificationDurationInSeconds = + IntegerType.create("typingNotificationDuration", 5); - private static final String KEY_SHOW_STAT_SHEET = "showStatSheet"; - private static final boolean DEFAULT_SHOW_STAT_SHEET = true; + public static final Preference chatNotificationBackground = + BooleanType.create("chatNotificationShowBackground", true); - private static final String KEY_SHOW_PORTRAIT = "showPortrait"; - private static final boolean DEFAULT_SHOW_PORTRAIT = true; + public static final Preference useToolTipForInlineRoll = + BooleanType.create("toolTipInlineRolls", false); - private static final String KEY_SHOW_STAT_SHEET_MODIFIER = "showStatSheetModifier"; - private static final boolean DEFAULT_SHOW_STAT_SHEET_MODIFIER = false; + public static final Preference suppressToolTipsForMacroLinks = + BooleanType.create("suppressToolTipsMacroLinks", false); - private static final String KEY_FILL_SELECTION_BOX = "fillSelectionBox"; - private static final boolean DEFAULT_FILL_SELECTION_BOX = true; + public static final Preference chatNotificationColor = + ColorType.create("chatNotificationColor", Color.white, false); - private static final String KEY_SHOW_INIT_GAIN_MESSAGE = "showInitGainMessage"; - private static final boolean DEFAULT_SHOW_INIT_GAIN_MESSAGE = true; + public static final Preference trustedPrefixBackground = + ColorType.create("trustedPrefixBG", new Color(0xD8, 0xE9, 0xF6), false); - private static final String KEY_FORCE_FACING_ARROW = "forceFacingArrow"; - private static final boolean DEFAULT_FORCE_FACING_ARROW = false; + public static final Preference trustedPrefixForeground = + ColorType.create("trustedPrefixFG", Color.BLACK, false); - private static final String KEY_USE_ASTAR_PATHFINDING = "useAstarPathfinding"; - private static final boolean DEFAULT_USE_ASTAR_PATHFINDING = true; + public static final Preference toolTipInitialDelay = + IntegerType.create("toolTipInitialDelay", 250); - private static final String KEY_VBL_BLOCKS_MOVE = "vblBlocksMove"; - private static final boolean DEFAULT_VBL_BLOCKS_MOVE = true; + public static final Preference toolTipDismissDelay = + IntegerType.create("toolTipDismissDelay", 30000); - private static final String MACRO_EDITOR_THEME = "macroEditorTheme"; - private static final String DEFAULT_MACRO_EDITOR_THEME = "Default"; + public static final Preference allowPlayerMacroEditsDefault = + BooleanType.create("allowPlayerMacroEditsDefault", true); - private static final String ICON_THEME = "iconTheme"; - private static final String DEFAULT_ICON_THEME = RessourceManager.ROD_TAKEHARA; + public static final Preference portraitSize = IntegerType.create("portraitSize", 175); - private static final String KEY_WEB_END_POINT_PORT = "webEndPointPort"; - private static final int DEFAULT_WEB_END_POINT = 654555; + public static final Preference thumbnailSize = IntegerType.create("thumbnailSize", 500); - public static void setFillSelectionBox(boolean fill) { - prefs.putBoolean(KEY_FILL_SELECTION_BOX, fill); - } - - public static boolean getFillSelectionBox() { - return prefs.getBoolean(KEY_FILL_SELECTION_BOX, DEFAULT_FILL_SELECTION_BOX); - } - - public static Color getChatColor() { - return new Color(prefs.getInt(KEY_CHAT_COLOR, DEFAULT_CHAT_COLOR.getRGB())); - } - - public static void setSaveReminder(boolean reminder) { - prefs.putBoolean(KEY_SAVE_REMINDER, reminder); - } - - public static boolean getSaveReminder() { - return prefs.getBoolean(KEY_SAVE_REMINDER, DEFAULT_SAVE_REMINDER); - } + public static final Preference showSmilies = BooleanType.create("insertSmilies", true); - // public static void setEnabledMapExportImport(boolean reminder) { - // prefs.putBoolean(KEY_ENABLE_MAP_EXPORT_IMPORT, reminder); - // AppActions.updateActions(); - // } + public static final Preference showDialogOnNewToken = + BooleanType.create("showDialogOnNewToken", true); - // public static boolean isEnabledMapExportImport() { - // return prefs.getBoolean(KEY_ENABLE_MAP_EXPORT_IMPORT, DEFAULT_ENABLE_MAP_EXPORT_IMPORT); - // } + public static final Preference showAvatarInChat = + BooleanType.create("showAvatarInChat", true); - public static void setAutoSaveIncrement(int increment) { - prefs.putInt(KEY_AUTO_SAVE_INCREMENT, increment); - } + public static final Preference playSystemSounds = + BooleanType.create("playSystemSounds", true); - public static int getAutoSaveIncrement() { - return prefs.getInt(KEY_AUTO_SAVE_INCREMENT, DEFAULT_AUTO_SAVE_INCREMENT); - } + public static final Preference playSystemSoundsOnlyWhenNotFocused = + BooleanType.create("playSystemSoundsOnlyWhenNotFocused", false); - public static void setChatAutosaveTime(int minutes) { - if (minutes >= 0) { - prefs.putInt(KEY_CHAT_AUTOSAVE_TIME, minutes); - ChatAutoSave.changeTimeout(minutes); - } - } + public static final Preference playStreams = BooleanType.create("playStreams", true); - public static int getChatAutosaveTime() { - return prefs.getInt(KEY_CHAT_AUTOSAVE_TIME, DEFAULT_CHAT_AUTOSAVE_TIME); - } + public static final Preference syrinscapeActive = + BooleanType.create("syrinscapeActive", false); - public static void setChatFilenameFormat(String pattern) { - prefs.put(KEY_CHAT_FILENAME_FORMAT, pattern); - } + public static final Preference fontSize = IntegerType.create("fontSize", 12); - public static String getChatFilenameFormat() { - return prefs.get(KEY_CHAT_FILENAME_FORMAT, DEFAULT_CHAT_FILENAME_FORMAT); - } + public static final Preference defaultGridColor = + ColorType.create("defaultGridColor", Color.black, false); - public static void clearChatFilenameFormat() { - prefs.remove(KEY_CHAT_FILENAME_FORMAT); - } + public static final Preference defaultGridSize = + IntegerType.create("defaultGridSize", 100); - public static void setTokenNumberDisplay(String display) { - prefs.put(KEY_TOKEN_NUMBER_DISPLAY, display); - } + public static final Preference defaultUnitsPerCell = + DoubleType.create("unitsPerCell", 5.); - public static String getTokenNumberDisplay() { - return prefs.get(KEY_TOKEN_NUMBER_DISPLAY, DEFAULT_TOKEN_NUMBER_DISPLAY); - } + public static final Preference faceVertex = BooleanType.create("faceVertex", false); - public static void setDuplicateTokenNumber(String numbering) { - prefs.put(KEY_DUPLICATE_TOKEN_NUMBER, numbering); - } + public static final Preference faceEdge = BooleanType.create("faceEdge", true); - public static String getDuplicateTokenNumber() { - return prefs.get(KEY_DUPLICATE_TOKEN_NUMBER, DEFAULT_DUPLICATE_TOKEN_NUMBER); - } + public static final Preference defaultVisionDistance = + IntegerType.create("defaultVisionDistance", 1000); - public static void setNewTokenNaming(String naming) { - prefs.put(KEY_NEW_TOKEN_NAMING, naming); - } + public static final Preference defaultVisionType = + EnumType.create(Zone.VisionType.class, "defaultVisionType", Zone.VisionType.OFF); - public static String getNewTokenNaming() { - return prefs.get(KEY_NEW_TOKEN_NAMING, DEFAULT_NEW_TOKEN_NAMING); - } + public static final Preference mapSortType = + EnumType.create(MapSortType.class, "sortByGMName", MapSortType.GMNAME); - public static void setUseHaloColorOnVisionOverlay(boolean flag) { - prefs.putBoolean(KEY_USE_HALO_COLOR_ON_VISION_OVERLAY, flag); - } + public static final Preference useSoftFogEdges = BooleanType.create("useSoftFog", true); - public static boolean getUseHaloColorOnVisionOverlay() { - return prefs.getBoolean( - KEY_USE_HALO_COLOR_ON_VISION_OVERLAY, DEFAULT_USE_HALO_COLOR_ON_VISION_OVERLAY); - } + public static final Preference newMapsHaveFow = + BooleanType.create("newMapsHaveFow", false); - public static void setMapVisibilityWarning(boolean flag) { - prefs.putBoolean(KEY_MAP_VISIBILITY_WARNING, flag); - } + public static final Preference newTokensVisible = + BooleanType.create("newTokensVisible", true); - public static boolean getMapVisibilityWarning() { - return prefs.getBoolean(KEY_MAP_VISIBILITY_WARNING, false); - } + public static final Preference newMapsVisible = + BooleanType.create("newMapsVisible", true); - public static void setAutoRevealVisionOnGMMovement(boolean flag) { - prefs.putBoolean(KEY_AUTO_REVEAL_VISION_ON_GM_MOVEMENT, flag); - } + public static final Preference newObjectsVisible = + BooleanType.create("newObjectsVisible", true); - public static boolean getAutoRevealVisionOnGMMovement() { - return prefs.getBoolean( - KEY_AUTO_REVEAL_VISION_ON_GM_MOVEMENT, DEFAULT_AUTO_REVEAL_VISION_ON_GM_MOVEMENT); - } + public static final Preference newBackgroundsVisible = + BooleanType.create("newBackgroundsVisible", true); - private static int range0to255(int value) { - return value < 1 ? 0 : Math.min(value, 255); - } + public static final Preference saveDirectory = + FileType.create("saveDir", () -> new File(File.separator)); - public static void setHaloOverlayOpacity(int size) { - prefs.putInt(KEY_HALO_OVERLAY_OPACITY, range0to255(size)); - } + public static final Preference tokenSaveDirectory = + FileType.create("saveTokenDir", saveDirectory::get); - public static int getHaloOverlayOpacity() { - int value = prefs.getInt(KEY_HALO_OVERLAY_OPACITY, DEFAULT_HALO_OVERLAY_OPACITY); - return range0to255(value); - } + public static final Preference mapSaveDirectory = + FileType.create("saveMapDir", saveDirectory::get); - public static void setAuraOverlayOpacity(int size) { - prefs.putInt(KEY_AURA_OVERLAY_OPACITY, range0to255(size)); - } + public static final Preference addOnLoadDirectory = + FileType.create("addOnLoadDir", saveDirectory::get); - public static int getAuraOverlayOpacity() { - int value = prefs.getInt(KEY_AURA_OVERLAY_OPACITY, DEFAULT_AURA_OVERLAY_OPACITY); - return range0to255(value); - } + public static final Preference loadDirectory = + FileType.create("loadDir", () -> new File(File.separator)); - public static void setLightOverlayOpacity(int size) { - prefs.putInt(KEY_LIGHT_OVERLAY_OPACITY, range0to255(size)); - } + public static final Preference renderQuality = + EnumType.create(RenderQuality.class, "renderScaleQuality", RenderQuality.LOW_SCALING) + .cacheIt(); - public static int getLightOverlayOpacity() { - int value = prefs.getInt(KEY_LIGHT_OVERLAY_OPACITY, DEFAULT_LIGHT_OVERLAY_OPACITY); - return range0to255(value); - } + /** The background color to use for NPC map labels. */ + public static final Preference npcMapLabelBackground = + ColorType.create("npcMapLabelBG", Color.LIGHT_GRAY, true); - public static void setLumensOverlayOpacity(int size) { - prefs.putInt(KEY_LUMENS_OVERLAY_OPACITY, range0to255(size)); - } + /** The foreground color to use for NPC map labels. */ + public static final Preference npcMapLabelForeground = + ColorType.create("npcMapLabelFG", Color.BLACK, true); - public static int getLumensOverlayOpacity() { - int value = prefs.getInt(KEY_LUMENS_OVERLAY_OPACITY, DEFAULT_LUMENS_OVERLAY_OPACITY); - return range0to255(value); - } + /** The border color to use for NPC map labels. */ + public static final Preference npcMapLabelBorder = + ColorType.create("mapLabelBorderColor", npcMapLabelForeground.getDefault(), true); - public static void setLumensOverlayBorderThickness(int thickness) { - prefs.putInt(KEY_LUMENS_OVERLAY_BORDER_THICKNESS, thickness); - } + /** The background color to use for PC map labels. */ + public static final Preference pcMapLabelBackground = + ColorType.create("pcMapLabelBG", Color.WHITE, true); - public static int getLumensOverlayBorderThickness() { - return prefs.getInt( - KEY_LUMENS_OVERLAY_BORDER_THICKNESS, DEFAULT_LUMENS_OVERLAY_BORDER_THICKNESS); - } + /** The foreground color to use for PC map labels. */ + public static final Preference pcMapLabelForeground = + ColorType.create("pcMapLabelFG", Color.BLUE, true); - public static void setLumensOverlayShowByDefault(boolean show) { - prefs.putBoolean(KEY_LUMENS_OVERLAY_SHOW_BY_DEFAULT, show); - } + /** The border color to use for PC map labels. */ + public static final Preference pcMapLabelBorder = + ColorType.create("pcMapLabelBorderColor", pcMapLabelForeground.getDefault(), true); - public static boolean getLumensOverlayShowByDefault() { - return prefs.getBoolean( - KEY_LUMENS_OVERLAY_SHOW_BY_DEFAULT, DEFAULT_LUMENS_OVERLAY_SHOW_BY_DEFAULT); - } + /** The background color to use for Non-Visible Token map labels. */ + public static final Preference nonVisibleTokenMapLabelBackground = + ColorType.create("nonVisMapLabelBG", Color.BLACK, true); - public static void setLightsShowByDefault(boolean show) { - prefs.putBoolean(KEY_LIGHTS_SHOW_BY_DEFAULT, show); - } + /** The foreground color to use for Non-Visible Token map labels. */ + public static final Preference nonVisibleTokenMapLabelForeground = + ColorType.create("nonVisMapLabelFG", Color.WHITE, true); - public static boolean getLightsShowByDefault() { - return prefs.getBoolean(KEY_LIGHTS_SHOW_BY_DEFAULT, DEFAULT_LIGHTS_SHOW_BY_DEFAULT); - } + /** The border color to use for Non-Visible Token map labels. */ + public static final Preference nonVisibleTokenMapLabelBorder = + ColorType.create( + "nonVisMapLabelBorderColor", nonVisibleTokenMapLabelForeground.getDefault(), true); - public static void setFogOverlayOpacity(int size) { - prefs.putInt(KEY_FOG_OVERLAY_OPACITY, range0to255(size)); + /** The font size to use for token map labels. */ + public static final Preference mapLabelFontSize = + IntegerType.create("mapLabelFontSize", AppStyle.labelFont.getSize()); - // FIXME Force ModelChange event to flush fog from zone :( - Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); - zone.setHasFog(zone.hasFog()); - } + /** The width of the border for token map labels, in pixels. */ + public static final Preference mapLabelBorderWidth = + IntegerType.create("mapLabelBorderWidth", Label.DEFAULT_LABEL_BORDER_WIDTH); - public static int getFogOverlayOpacity() { - int value = prefs.getInt(KEY_FOG_OVERLAY_OPACITY, DEFAULT_FOG_OVERLAY_OPACITY); - return range0to255(value); - } + /** The size of the border arc for token map labels. */ + public static final Preference mapLabelBorderArc = + IntegerType.create("mapLabelBorderArc", Label.DEFAULT_LABEL_BORDER_ARC); - private static final String KEY_DEFAULT_GRID_TYPE = "defaultGridType"; - private static final String DEFAULT_DEFAULT_GRID_TYPE = GridFactory.SQUARE; + /** {@code true} if borders should be shown around map labels, {@code false} otherwise. */ + public static final Preference mapLabelShowBorder = + BooleanType.create("mapLabelShowBorder", true); - private static final String KEY_FACE_VERTEX = "faceVertex"; - private static final boolean DEFAULT_FACE_VERTEX = false; + // TODO Why is the default not a valid port? + public static final Preference webEndpointPort = + IntegerType.create("webEndPointPort", 654555); - private static final String KEY_FACE_EDGE = "faceEdge"; - private static final boolean DEFAULT_FACE_EDGE = true; + public static final Preference tokensWarnWhenDeleted = + BooleanType.create("tokensWarnWhenDeleted", true); - private static final String KEY_DEFAULT_GRID_SIZE = "defaultGridSize"; - private static final int DEFAULT_DEFAULT_GRID_SIZE = 100; + public static final Preference drawingsWarnWhenDeleted = + BooleanType.create("drawWarnWhenDeleted", true); - private static final String KEY_DEFAULT_GRID_COLOR = "defaultGridColor"; - private static final int DEFAULT_DEFAULT_GRID_COLOR = Color.black.getRGB(); + public static final Preference tokensSnapWhileDragging = + BooleanType.create("tokensSnapWhileDragging", true); - private static final String KEY_DEFAULT_UNITS_PER_CELL = "unitsPerCell"; - private static final int DEFAULT_DEFAULT_UNITS_PER_CELL = 5; + public static final Preference hideMousePointerWhileDragging = + BooleanType.create("hideMousePointerWhileDragging", true); - private static final String KEY_DEFAULT_VISION_DISTANCE = "defaultVisionDistance"; - private static final int DEFAULT_DEFAULT_VISION_DISTANCE = 1000; + public static final Preference hideTokenStackIndicator = + BooleanType.create("hideTokenStackIndicator", false); - private static final String KEY_DEFAULT_VISION_TYPE = "defaultVisionType"; - private static final Zone.VisionType DEFAULT_VISION_TYPE = Zone.VisionType.OFF; + public static final Preference tokensStartSnapToGrid = + BooleanType.create("newTokensStartSnapToGrid", true); - private static final String KEY_MAP_SORT_TYPE = "sortByGMName"; - private static final MapSortType DEFAULT_MAP_SORT_TYPE = MapSortType.GMNAME; + public static final Preference objectsStartSnapToGrid = + BooleanType.create("newStampsStartSnapToGrid", false); - private static final String KEY_FONT_SIZE = "fontSize"; - private static final int DEFAULT_FONT_SIZE = 12; + public static final Preference backgroundsStartSnapToGrid = + BooleanType.create("newBackgroundsStartSnapToGrid", false); - private static final String KEY_CHAT_COLOR = "chatColor"; - private static final Color DEFAULT_CHAT_COLOR = Color.black; + public static final Preference tokensStartFreesize = + BooleanType.create("newTokensStartFreesize", false); - private static final String KEY_PLAY_SYSTEM_SOUNDS = "playSystemSounds"; - private static final boolean DEFAULT_PLAY_SYSTEM_SOUNDS = true; + public static final Preference objectsStartFreesize = + BooleanType.create("newStampsStartFreesize", true); - private static final String KEY_PLAY_STREAMS = "playStreams"; - private static final boolean DEFAULT_PLAY_STREAMS = true; + public static final Preference backgroundsStartFreesize = + BooleanType.create("newBackgroundsStartFreesize", true); - /** - * The key for retrieving the background color of NPC map labels. The value of this key is used to - * store and retrieve background color information for NPC map The background color is used to - * style the text of the map labels for Non-Player Characters (NPCs). labels. The value associated - * with this key should be a valid color value. - */ - private static final String KEY_NPC_MAP_LABEL_BG_COLOR = "npcMapLabelBG"; + public static final Preference defaultGridType = + StringType.create("defaultGridType", GridFactory.SQUARE); - /** - * Constant variable for the foreground color of NPC map labels. The value represents the key used - * to retrieve the color from a map or configuration file. The foreground color is used to style - * the text of the map labels for Non-Player Characters (NPCs). This constant is intended to be - * used within the context of a software application or system. - */ - private static final String KEY_NPC_MAP_LABEL_FG_COLOR = "npcMapLabelFG"; + public static final Preference showStatSheet = BooleanType.create("showStatSheet", true); - /** - * Constant variable for the border color of NPC map labels. The value represents the key used to - * retrieve the color from a map or configuration file. The foreground color is used to style the - * text of the map labels for Non-Player Characters (NPCs). This constant is intended to be used - * within the context of a software application or system. - */ - private static final String KEY_NPC_MAP_LABEL_BORDER_COLOR = "mapLabelBorderColor"; + public static final Preference showStatSheetRequiresModifierKey = + BooleanType.create("showStatSheetModifier", false); - /** - * The key for retrieving the background color of PC map labels. The value of this key is used to - * store and retrieve background color information for NPC map The background color is used to - * style the text of the map labels for Player Characters (PCs) labels. The value associated with - * this key should be a valid color value. - */ - private static final String KEY_PC_MAP_LABEL_BG_COLOR = "pcMapLabelBG"; + public static final Preference showPortrait = BooleanType.create("showPortrait", true); - /** - * Constant variable for the foreground color of NPC map labels. The value represents the key used - * to retrieve the color from a map or configuration file. The border color is used to style the - * text of the map labels for Non Player Characters (NPCs). - */ - private static final String KEY_PC_MAP_LABEL_FG_COLOR = "pcMapLabelFG"; + public static final Preference forceFacingArrow = + BooleanType.create("forceFacingArrow", false); - /** - * Constant variable for the foreground color of PC map labels. The value represents the key used - * to retrieve the color from a map or configuration file. The border color is used to style the - * text of the map labels for Player Characters (PCs). - */ - private static final String KEY_PC_MAP_LABEL_BORDER_COLOR = "pcMapLabelBorderColor"; + public static final Preference fitGmView = BooleanType.create("fitGMView", true); - /** - * This variable represents the key used to store the background color of non-visible token map - * labels. The background color is used to style the text of the map labels for tokens that are - * not visible to the player. The value associated with this key should be a valid color value. - */ - private static final String KEY_NONVIS_MAP_LABEL_BG_COLOR = "nonVisMapLabelBG"; + public static final Preference defaultUserName = + StringType.create( + "defaultUsername", I18N.getString("Preferences.client.default.username.value")); - /** - * This variable represents the key used to store the foreground color of non-visible token map - * labels. The foreground color is used to style the text of the map labels for tokens that are - * not visible to the player. The value associated with this key should be a valid color value. - */ - private static final String KEY_NONVIS_MAP_LABEL_FG_COLOR = "nonVisMapLabelFG"; + public static final Preference movementMetric = + EnumType.create(WalkerMetric.class, "movementMetric", WalkerMetric.ONE_TWO_ONE); - /** - * This variable represents the key used to store the border color of non-visible token map - * labels. The foreground color is used to style the text of the map labels for tokens that are - * not visible to the player. The value associated with this key should be a valid color value. - */ - private static final String KEY_NONVIS_MAP_LABEL_BORDER_COLOR = "nonVisMapLabelBorderColor"; + public static final Preference frameRateCap = + IntegerType.create("frameRateCap", 60).validateIt(cap -> cap > 0); - /** - * The KEY_MAP_LABEL_FONT_SIZE constant is used to define the name of the key that represents the - * font size of map labels. - */ - private static final String KEY_MAP_LABEL_FONT_SIZE = "mapLabelFontSize"; + public static final Preference upnpDiscoveryTimeout = + IntegerType.create("upnpDiscoveryTimeout", 5000); - /** The configuration key for specifying the width of the border around map labels for tokens. */ - private static final String KEY_MAP_LABEL_BORDER_WIDTH = "mapLabelBorderWidth"; + public static final Preference fileSyncPath = StringType.create("fileSyncPath", ""); - /** The configuration key for specifying the arc of the border around map labels for tokens. */ - private static final String KEY_MAP_LABEL_BORDER_ARC = "mapLabelBorderArc"; + public static final Preference skipAutoUpdate = + BooleanType.create("skipAutoUpdate", false); - /** The configuration key for specifying the width of the border around map labels for tokens. */ - private static final String KEY_MAP_LABEL_SHOW_BORDER = "mapLabelShowBorder"; + public static final Preference skipAutoUpdateRelease = + StringType.create("skipAutoUpdateRelease", ""); - /** The default background color for the NPC map label. */ - private static final Color DEFAULT_NPC_MAP_LABEL_BG_COLOR = Color.LIGHT_GRAY; + public static final Preference allowExternalMacroAccess = + BooleanType.create("allowExternalMacroAccess", false); - /** The default foreground color for NPC map labels. */ - private static final Color DEFAULT_NPC_MAP_LABEL_FG_COLOR = Color.BLACK; + public static final Preference loadMruCampaignAtStart = + BooleanType.create("loadMRUCampaignAtStart", false); - /** The default border color for NPC map labels. */ - private static final Color DEFAULT_NPC_MAP_LABEL_BORDER_COLOR = DEFAULT_NPC_MAP_LABEL_FG_COLOR; + public static final Preference initiativePanelShowsTokenImage = + BooleanType.create("initShowTokens", true); - /** The default background color for the PC map label. */ - private static final Color DEFAULT_PC_MAP_LABEL_BG_COLOR = Color.WHITE; + public static final Preference initiativePanelShowsTokenState = + BooleanType.create("initShowTokenStates", true); - /** The default foreground color for the map labels in the PC map. */ - private static final Color DEFAULT_PC_MAP_LABEL_FG_COLOR = Color.BLUE; + public static final Preference initiativePanelShowsInitiative = + BooleanType.create("initShowInitiative", true); - /** The default border color for the PC map labels. */ - private static final Color DEFAULT_PC_MAP_LABEL_BORDER_COLOR = DEFAULT_PC_MAP_LABEL_FG_COLOR; + public static final Preference initiativePanelShowsInitiativeOnLine2 = + BooleanType.create("initShow2ndLine", false); - /** The default background color for non-visible map labels. */ - private static final Color DEFAULT_NONVIS_MAP_LABEL_BG_COLOR = Color.BLACK; + public static final Preference initiativePanelHidesNpcs = + BooleanType.create("initHideNpcs", false); - /** The default foreground color for non-visible map labels. */ - private static final Color DEFAULT_NONVIS_MAP_LABEL_FG_COLOR = Color.WHITE; + public static final Preference initiativePanelAllowsOwnerPermissions = + BooleanType.create("initOwnerPermissions", false); - /** The default border color for non-visible map labels. */ - private static final Color DEFAULT_NONVIS_MAP_LABEL_BORDER_COLOR = - DEFAULT_NONVIS_MAP_LABEL_FG_COLOR; + public static final Preference initiativeMovementLocked = + BooleanType.create("initLockMovement", false); - /** The default font size for map labels. */ - private static final int DEFAULT_MAP_LABEL_FONT_SIZE = AppStyle.labelFont.getSize(); + public static final Preference showInitiativeGainedMessage = + BooleanType.create("showInitGainMessage", true); - /** The default border width for token map labels. */ - private static final int DEFAULT_MAP_LABEL_BORDER_WIDTH = Label.DEFAULT_LABEL_BORDER_WIDTH; + public static final Preference pathfindingEnabled = + BooleanType.create("useAstarPathfinding", true); - /** The default border arc for token map labels. */ - private static final int DEFAULT_MAP_LABEL_BORDER_ARC = Label.DEFAULT_LABEL_BORDER_ARC; + public static final Preference pathfindingBlockedByVbl = + BooleanType.create("vblBlocksMove", true); - /** The default border arc for token map labels. */ - private static final boolean DEFAULT_MAP_LABEL_SHOW_BORDER = true; + public static final Preference defaultMacroEditorTheme = + StringType.create("macroEditorTheme", "Default"); - public static void setHaloLineWidth(int size) { - prefs.putInt(KEY_HALO_LINE_WIDTH, size); - } + public static final Preference iconTheme = StringType.create("iconTheme", "Rod Takehara"); - public static int getHaloLineWidth() { - return prefs.getInt(KEY_HALO_LINE_WIDTH, DEFAULT_HALO_LINE_WIDTH); + static { + // Used to be stored as separate components but now is one color. Add if not already there. + if (prefs.get("trustedPrefixFG", null) == null) { + var defaultValue = trustedPrefixForeground.getDefault(); + trustedPrefixForeground.set( + new Color( + prefs.getInt("trustedPrefixFGRed", defaultValue.getRed()), + prefs.getInt("trustedPrefixFGGreen", defaultValue.getGreen()), + prefs.getInt("trustedPrefixFBlue", defaultValue.getBlue()))); + } + if (prefs.get("trustedPrefixBG", null) == null) { + var defaultValue = trustedPrefixBackground.getDefault(); + trustedPrefixBackground.set( + new Color( + prefs.getInt("trustedPrefixBGRed", defaultValue.getRed()), + prefs.getInt("trustedPrefixBGGreen", defaultValue.getGreen()), + prefs.getInt("trustedPrefixBBlue", defaultValue.getBlue()))); + } + if (prefs.get("chatNotificationColor", null) == null) { + var defaultValue = chatNotificationColor.getDefault(); + chatNotificationColor.set( + new Color( + prefs.getInt("chatNotificationColorRed", defaultValue.getRed()), + prefs.getInt("chatNotificationColorGreen", defaultValue.getGreen()), + prefs.getInt("chatNotificationColorBlue", defaultValue.getBlue()))); + } } - private static final String KEY_PORTRAIT_SIZE = "portraitSize"; - private static final int DEFAULT_PORTRAIT_SIZE = 175; - - private static final String KEY_THUMBNAIL_SIZE = "thumbnailSize"; - private static final int DEFAULT_THUMBNAIL_SIZE = 500; - - private static final String KEY_ALLOW_PLAYER_MACRO_EDITS_DEFAULT = "allowPlayerMacroEditsDefault"; - private static final boolean DEFAULT_ALLOW_PLAYER_MACRO_EDITS_DEFAULT = true; - - private static final String KEY_TOOLTIP_INITIAL_DELAY = "toolTipInitialDelay"; - private static final int DEFAULT_TOOLTIP_INITIAL_DELAY = 250; - - private static final String KEY_TOOLTIP_DISMISS_DELAY = "toolTipDismissDelay"; - private static final int DEFAULT_TOOLTIP_DISMISS_DELAY = 30000; - - private static final String KEY_TOOLTIP_FOR_INLINE_ROLLS = "toolTipInlineRolls"; - private static final boolean DEFAULT_TOOLTIP_FOR_INLINE_ROLLS = false; - - private static final String KEY_SUPPRESS_TOOLTIPS_FOR_MACROLINKS = "suppressToolTipsMacroLinks"; - private static final boolean DEFAULT_SUPPRESS_TOOLTIPS_FOR_MACROLINKS = false; - - // chat notification colors - private static final String KEY_CHAT_NOTIFICATION_COLOR_RED = "chatNotificationColorRed"; - private static final int DEFAULT_CHAT_NOTIFICATION_COLOR_RED = 0xFF; - - private static final String KEY_CHAT_NOTIFICATION_COLOR_GREEN = "chatNotificationColorGreen"; - private static final int DEFAULT_CHAT_NOTIFICATION_COLOR_GREEN = 0xFF; - - private static final String KEY_CHAT_NOTIFICATION_COLOR_BLUE = "chatNotificationColorBlue"; - private static final int DEFAULT_CHAT_NOTIFICATION_COLOR_BLUE = 0xFF; - - // end chat notification colors - - private static final String KEY_CHAT_NOTIFICATION_SHOW_BACKGROUND = - "chatNotificationShowBackground"; - private static final boolean DEFAULT_CHAT_NOTIFICATION_SHOW_BACKGROUND = true; - - private static final String KEY_TRUSTED_PREFIX_BG_RED = "trustedPrefixBGRed"; - private static final int DEFAULT_TRUSTED_PREFIX_BG_RED = 0xD8; - - private static final String KEY_TRUSTED_PREFIX_BG_GREEN = "trustedPrefixBGGreen"; - private static final int DEFAULT_TRUSTED_PREFIX_BG_GREEN = 0xE9; - - private static final String KEY_TRUSTED_PREFIX_BG_BLUE = "trustedPrefixBBlue"; - private static final int DEFAULT_TRUSTED_PREFIX_BG_BLUE = 0xF6; - - private static final String KEY_TRUSTED_PREFIX_FG_RED = "trustedPrefixFGRed"; - private static final int DEFAULT_TRUSTED_PREFIX_FG_RED = 0x00; - - private static final String KEY_TRUSTED_PREFIX_FG_GREEN = "trustedPrefixFGGreen"; - private static final int DEFAULT_TRUSTED_PREFIX_FG_GREEN = 0x00; - - private static final String KEY_TRUSTED_PREFIX_FG_BLUE = "trustedPrefixFBlue"; - private static final int DEFAULT_TRUSTED_PREFIX_FG_BLUE = 0x00; - - private static final String KEY_FIT_GM_VIEW = "fitGMView"; - private static final boolean DEFAULT_FIT_GM_VIEW = true; - - private static final String KEY_DEFAULT_USERNAME = "defaultUsername"; - private static final String DEFAULT_USERNAME = - I18N.getString("Preferences.client.default.username.value"); - - private static final String KEY_TYPING_NOTIFICATION_DURATION = "typingNotificationDuration"; - private static final int DEFAULT_TYPING_NOTIFICATION_DURATION = 5000; - - private static final String KEY_FRAME_RATE_CAP = "frameRateCap"; - private static final int DEFAULT_FRAME_RATE_CAP = 60; - - private static final String KEY_UPNP_DISCOVERY_TIMEOUT = "upnpDiscoveryTimeout"; - private static final int DEFAULT_UPNP_DISCOVERY_TIMEOUT = 5000; - - private static final String KEY_FILE_SYNC_PATH = "fileSyncPath"; - private static final String DEFAULT_FILE_SYNC_PATH = ""; - - private static final String KEY_SKIP_AUTO_UPDATE = "skipAutoUpdate"; - private static final boolean DEFAULT_SKIP_AUTO_UPDATE = false; - private static final String KEY_SKIP_AUTO_UPDATE_RELEASE = "skipAutoUpdateRelease"; - private static final String DEFAULT_SKIP_AUTO_UPDATE_RELEASE = ""; - - private static final String KEY_ALLOW_EXTERNAL_MACRO_ACCESS = "allowExternalMacroAccess"; - private static final boolean DEFAULT_ALLOW_EXTERNAL_MACRO_ACCESS = false; - - private static final String KEY_RENDER_QUALITY = "renderScaleQuality"; - public enum RenderQuality { LOW_SCALING, PIXEL_ART_SCALING, @@ -800,743 +478,17 @@ public int getResampleOpFilter() { } } - public static void setRenderQuality(RenderQuality quality) { - prefs.put(KEY_RENDER_QUALITY, quality.name()); - renderQuality = quality; - } - - public static RenderQuality getRenderQuality() { - if (renderQuality == null) { - try { - renderQuality = - RenderQuality.valueOf(prefs.get(KEY_RENDER_QUALITY, RenderQuality.LOW_SCALING.name())); - } catch (Exception e) { - renderQuality = RenderQuality.LOW_SCALING; - } - } - return renderQuality; - } - - public static void setTypingNotificationDuration(int ms) { - prefs.putInt(KEY_TYPING_NOTIFICATION_DURATION, ms); - MapTool.getFrame().setChatNotifyDuration(ms); - } - - public static Integer getTypingNotificationDuration() { - Integer value = - prefs.getInt(KEY_TYPING_NOTIFICATION_DURATION, DEFAULT_TYPING_NOTIFICATION_DURATION); - return value; - } - - public static void setUseToolTipForInlineRoll(boolean tooltip) { - prefs.putBoolean(KEY_TOOLTIP_FOR_INLINE_ROLLS, tooltip); - } - - public static boolean getUseToolTipForInlineRoll() { - return prefs.getBoolean(KEY_TOOLTIP_FOR_INLINE_ROLLS, DEFAULT_TOOLTIP_FOR_INLINE_ROLLS); - } - - public static void setSuppressToolTipsForMacroLinks(boolean tooltip) { - prefs.putBoolean(KEY_SUPPRESS_TOOLTIPS_FOR_MACROLINKS, tooltip); - } - - public static boolean getSuppressToolTipsForMacroLinks() { - return prefs.getBoolean( - KEY_SUPPRESS_TOOLTIPS_FOR_MACROLINKS, DEFAULT_SUPPRESS_TOOLTIPS_FOR_MACROLINKS); - } - - public static void setChatNotificationColor(Color color) { - prefs.putInt(KEY_CHAT_NOTIFICATION_COLOR_RED, color.getRed()); - prefs.putInt(KEY_CHAT_NOTIFICATION_COLOR_GREEN, color.getGreen()); - prefs.putInt(KEY_CHAT_NOTIFICATION_COLOR_BLUE, color.getBlue()); - } - - public static Color getChatNotificationColor() { - return new Color( - prefs.getInt(KEY_CHAT_NOTIFICATION_COLOR_RED, DEFAULT_CHAT_NOTIFICATION_COLOR_RED), - prefs.getInt(KEY_CHAT_NOTIFICATION_COLOR_GREEN, DEFAULT_CHAT_NOTIFICATION_COLOR_GREEN), - prefs.getInt(KEY_CHAT_NOTIFICATION_COLOR_BLUE, DEFAULT_CHAT_NOTIFICATION_COLOR_BLUE)); - } - - public static void setTrustedPrefixBG(Color color) { - prefs.putInt(KEY_TRUSTED_PREFIX_BG_RED, color.getRed()); - prefs.putInt(KEY_TRUSTED_PREFIX_BG_GREEN, color.getGreen()); - prefs.putInt(KEY_TRUSTED_PREFIX_BG_BLUE, color.getBlue()); - } - - public static Color getTrustedPrefixBG() { - return new Color( - prefs.getInt(KEY_TRUSTED_PREFIX_BG_RED, DEFAULT_TRUSTED_PREFIX_BG_RED), - prefs.getInt(KEY_TRUSTED_PREFIX_BG_GREEN, DEFAULT_TRUSTED_PREFIX_BG_GREEN), - prefs.getInt(KEY_TRUSTED_PREFIX_BG_BLUE, DEFAULT_TRUSTED_PREFIX_BG_BLUE)); - } - - public static void setTrustedPrefixFG(Color color) { - prefs.putInt(KEY_TRUSTED_PREFIX_FG_RED, color.getRed()); - prefs.putInt(KEY_TRUSTED_PREFIX_FG_GREEN, color.getGreen()); - prefs.putInt(KEY_TRUSTED_PREFIX_FG_BLUE, color.getBlue()); - } - - public static Color getTrustedPrefixFG() { - return new Color( - prefs.getInt(KEY_TRUSTED_PREFIX_FG_RED, DEFAULT_TRUSTED_PREFIX_FG_RED), - prefs.getInt(KEY_TRUSTED_PREFIX_FG_GREEN, DEFAULT_TRUSTED_PREFIX_FG_GREEN), - prefs.getInt(KEY_TRUSTED_PREFIX_FG_BLUE, DEFAULT_TRUSTED_PREFIX_FG_BLUE)); - } - - public static void setToolTipInitialDelay(int ms) { - prefs.putInt(KEY_TOOLTIP_INITIAL_DELAY, ms); - } - - public static int getToolTipInitialDelay() { - return prefs.getInt(KEY_TOOLTIP_INITIAL_DELAY, DEFAULT_TOOLTIP_INITIAL_DELAY); - } - - public static void setToolTipDismissDelay(int ms) { - prefs.putInt(KEY_TOOLTIP_DISMISS_DELAY, ms); - } - - public static int getToolTipDismissDelay() { - return prefs.getInt(KEY_TOOLTIP_DISMISS_DELAY, DEFAULT_TOOLTIP_DISMISS_DELAY); - } - - public static void setAllowPlayerMacroEditsDefault(boolean show) { - prefs.putBoolean(KEY_ALLOW_PLAYER_MACRO_EDITS_DEFAULT, show); - } - - public static boolean getAllowPlayerMacroEditsDefault() { - return prefs.getBoolean( - KEY_ALLOW_PLAYER_MACRO_EDITS_DEFAULT, DEFAULT_ALLOW_PLAYER_MACRO_EDITS_DEFAULT); - } - - public static void setPortraitSize(int size) { - prefs.putInt(KEY_PORTRAIT_SIZE, size); - } - - public static int getPortraitSize() { - return prefs.getInt(KEY_PORTRAIT_SIZE, DEFAULT_PORTRAIT_SIZE); - } - - public static void setThumbnailSize(int size) { - prefs.putInt(KEY_THUMBNAIL_SIZE, size); - } - - public static int getThumbnailSize() { - return prefs.getInt(KEY_THUMBNAIL_SIZE, DEFAULT_THUMBNAIL_SIZE); - } - - public static void setShowSmilies(boolean show) { - prefs.putBoolean(KEY_INSERT_SMILIES, show); - } - - public static boolean getShowSmilies() { - return prefs.getBoolean(KEY_INSERT_SMILIES, DEFAULT_SHOW_SMILIES); - } - - public static void setShowDialogOnNewToken(boolean show) { - prefs.putBoolean(KEY_SHOW_DIALOG_ON_NEW_TOKEN, show); - } - - public static boolean getShowDialogOnNewToken() { - return prefs.getBoolean(KEY_SHOW_DIALOG_ON_NEW_TOKEN, DEFAULT_SHOW_DIALOG_ON_NEW_TOKEN); - } - - public static void setShowAvatarInChat(boolean show) { - prefs.putBoolean(KEY_SHOW_AVATAR_IN_CHAT, show); - } - - public static boolean getShowAvatarInChat() { - return prefs.getBoolean(KEY_SHOW_AVATAR_IN_CHAT, DEFAULT_SHOW_AVATAR_IN_CHAT); - } - - public static void setPlaySystemSounds(boolean play) { - prefs.putBoolean(KEY_PLAY_SYSTEM_SOUNDS, play); - } - - public static void setPlayStreams(boolean play) { - prefs.putBoolean(KEY_PLAY_STREAMS, play); - } - - public static boolean getPlaySystemSounds() { - return prefs.getBoolean(KEY_PLAY_SYSTEM_SOUNDS, DEFAULT_PLAY_SYSTEM_SOUNDS); - } - - public static boolean getPlayStreams() { - return prefs.getBoolean(KEY_PLAY_STREAMS, DEFAULT_PLAY_STREAMS); - } - - public static void setPlaySystemSoundsOnlyWhenNotFocused(boolean play) { - prefs.putBoolean(KEY_SOUNDS_ONLY_WHEN_NOT_FOCUSED, play); - } - - public static boolean getPlaySystemSoundsOnlyWhenNotFocused() { - return prefs.getBoolean(KEY_SOUNDS_ONLY_WHEN_NOT_FOCUSED, DEFAULT_SOUNDS_ONLY_WHEN_NOT_FOCUSED); - } - - public static void setSyrinscapeActive(boolean active) { - prefs.putBoolean(KEY_SYRINSCAPE_ACTIVE, active); - } - - public static boolean getSyrinscapeActive() { - return prefs.getBoolean(KEY_SYRINSCAPE_ACTIVE, DEFAULT_SYRINSCAPE_ACTIVE); - } - - public static void setChatColor(Color color) { - prefs.putInt(KEY_CHAT_COLOR, color.getRGB()); - } - - public static void setFontSize(int size) { - prefs.putInt(KEY_FONT_SIZE, size); - } + // Based off vision type enum in Zone.java, this could easily get tossed somewhere else if + // preferred. + public enum MapSortType { + DISPLAYNAME(), + GMNAME(); - public static int getFontSize() { - return prefs.getInt(KEY_FONT_SIZE, DEFAULT_FONT_SIZE); - } + private final String displayName; - public static void setDefaultGridColor(Color color) { - prefs.putInt(KEY_DEFAULT_GRID_COLOR, color.getRGB()); - } - - public static Color getDefaultGridColor() { - return new Color(prefs.getInt(KEY_DEFAULT_GRID_COLOR, DEFAULT_DEFAULT_GRID_COLOR)); - } - - public static boolean getFaceVertex() { - return prefs.getBoolean(KEY_FACE_VERTEX, DEFAULT_FACE_VERTEX); - } - - public static void setFaceVertex(boolean yesNo) { - prefs.putBoolean(KEY_FACE_VERTEX, yesNo); - } - - public static boolean getFaceEdge() { - return prefs.getBoolean(KEY_FACE_EDGE, DEFAULT_FACE_EDGE); - } - - public static void setFaceEdge(boolean yesNo) { - prefs.putBoolean(KEY_FACE_EDGE, yesNo); - } - - public static void clearAssetRoots() { - prefs.put(KEY_ASSET_ROOTS, ""); - } - - public static void setSaveDir(File file) { - prefs.put(KEY_SAVE_DIR, file.toString()); - } - - public static void setDefaultGridSize(int size) { - prefs.putInt(KEY_DEFAULT_GRID_SIZE, size); - } - - public static int getDefaultGridSize() { - return prefs.getInt(KEY_DEFAULT_GRID_SIZE, DEFAULT_DEFAULT_GRID_SIZE); - } - - public static void setDefaultUnitsPerCell(double size) { - prefs.putDouble(KEY_DEFAULT_UNITS_PER_CELL, size); - } - - public static double getDefaultUnitsPerCell() { - return prefs.getDouble(KEY_DEFAULT_UNITS_PER_CELL, DEFAULT_DEFAULT_UNITS_PER_CELL); - } - - public static void setDefaultVisionDistance(int dist) { - prefs.putInt(KEY_DEFAULT_VISION_DISTANCE, dist); - } - - public static int getDefaultVisionDistance() { - return prefs.getInt(KEY_DEFAULT_VISION_DISTANCE, DEFAULT_DEFAULT_VISION_DISTANCE); - } - - public static void setDefaultVisionType(Zone.VisionType visionType) { - prefs.put(KEY_DEFAULT_VISION_TYPE, visionType.name()); - } - - public static void setMapSortType(MapSortType mapSortType) { - prefs.put(KEY_MAP_SORT_TYPE, mapSortType.name()); - } - - public static Zone.VisionType getDefaultVisionType() { - try { - return Zone.VisionType.valueOf( - prefs.get(KEY_DEFAULT_VISION_TYPE, DEFAULT_VISION_TYPE.name())); - } catch (Exception e) { - return DEFAULT_VISION_TYPE; - } - } - - public static MapSortType getMapSortType() { - try { - return MapSortType.valueOf(prefs.get(KEY_MAP_SORT_TYPE, DEFAULT_MAP_SORT_TYPE.name())); - } catch (Exception e) { - return DEFAULT_MAP_SORT_TYPE; - } - } - - public static void setUseSoftFogEdges(boolean flag) { - prefs.putBoolean(KEY_USE_SOFT_FOG_EDGES, flag); - } - - public static boolean getUseSoftFogEdges() { - return prefs.getBoolean(KEY_USE_SOFT_FOG_EDGES, DEFAULT_USE_SOFT_FOG_EDGES); - } - - public static void setNewMapsHaveFOW(boolean flag) { - prefs.putBoolean(KEY_NEW_MAPS_HAVE_FOW, flag); - } - - public static boolean getNewMapsHaveFOW() { - return prefs.getBoolean(KEY_NEW_MAPS_HAVE_FOW, DEFAULT_NEW_MAPS_HAVE_FOW); - } - - public static void setNewTokensVisible(boolean flag) { - prefs.putBoolean(KEY_NEW_TOKENS_VISIBLE, flag); - } - - public static boolean getNewTokensVisible() { - return prefs.getBoolean(KEY_NEW_TOKENS_VISIBLE, DEFAULT_NEW_TOKENS_VISIBLE); - } - - public static void setNewMapsVisible(boolean flag) { - prefs.putBoolean(KEY_NEW_MAPS_VISIBLE, flag); - } - - public static boolean getNewMapsVisible() { - return prefs.getBoolean(KEY_NEW_MAPS_VISIBLE, DEFAULT_NEW_MAPS_VISIBLE); - } - - public static void setNewObjectsVisible(boolean flag) { - prefs.putBoolean(KEY_NEW_OBJECTS_VISIBLE, flag); - } - - public static boolean getNewObjectsVisible() { - return prefs.getBoolean(KEY_NEW_OBJECTS_VISIBLE, DEFAULT_NEW_OBJECTS_VISIBLE); - } - - public static void setNewBackgroundsVisible(boolean flag) { - prefs.putBoolean(KEY_NEW_BACKGROUNDS_VISIBLE, flag); - } - - public static boolean getNewBackgroundsVisible() { - return prefs.getBoolean(KEY_NEW_BACKGROUNDS_VISIBLE, DEFAULT_NEW_BACKGROUNDS_VISIBLE); - } - - public static void setTokensWarnWhenDeleted(boolean flag) { - prefs.putBoolean(KEY_TOKENS_WARN_WHEN_DELETED, flag); - } - - public static boolean getTokensWarnWhenDeleted() { - return prefs.getBoolean(KEY_TOKENS_WARN_WHEN_DELETED, DEFAULT_TOKENS_WARN_WHEN_DELETED); - } - - public static void setDrawWarnWhenDeleted(boolean flag) { - prefs.putBoolean(KEY_DRAW_WARN_WHEN_DELETED, flag); - } - - public static boolean getDrawWarnWhenDeleted() { - return prefs.getBoolean(KEY_DRAW_WARN_WHEN_DELETED, DEFAULT_DRAW_WARN_WHEN_DELETED); - } - - public static void setTokensStartSnapToGrid(boolean flag) { - prefs.putBoolean(KEY_TOKENS_START_SNAP_TO_GRID, flag); - } - - public static boolean getTokensStartSnapToGrid() { - return prefs.getBoolean(KEY_TOKENS_START_SNAP_TO_GRID, DEFAULT_TOKENS_START_SNAP_TO_GRID); - } - - public static void setTokensSnapWhileDragging(boolean flag) { - prefs.putBoolean(KEY_TOKENS_SNAP_WHILE_DRAGGING, flag); - } - - public static boolean getTokensSnapWhileDragging() { - return prefs.getBoolean(KEY_TOKENS_SNAP_WHILE_DRAGGING, DEFAULT_KEY_TOKENS_SNAP_WHILE_DRAGGING); - } - - public static void setHideMousePointerWhileDragging(boolean flag) { - prefs.putBoolean(KEY_HIDE_MOUSE_POINTER_WHILE_DRAGGING, flag); - } - - public static boolean getHideMousePointerWhileDragging() { - return prefs.getBoolean( - KEY_HIDE_MOUSE_POINTER_WHILE_DRAGGING, DEFAULT_KEY_HIDE_MOUSE_POINTER_WHILE_DRAGGING); - } - - public static void setHideTokenStackIndicator(boolean flag) { - prefs.putBoolean(KEY_HIDE_TOKEN_STACK_INDICATOR, flag); - } - - public static boolean getHideTokenStackIndicator() { - return prefs.getBoolean(KEY_HIDE_TOKEN_STACK_INDICATOR, DEFAULT_KEY_HIDE_TOKEN_STACK_INDICATOR); - } - - public static void setObjectsStartSnapToGrid(boolean flag) { - prefs.putBoolean(KEY_OBJECTS_START_SNAP_TO_GRID, flag); - } - - public static boolean getObjectsStartSnapToGrid() { - return prefs.getBoolean(KEY_OBJECTS_START_SNAP_TO_GRID, DEFAULT_OBJECTS_START_SNAP_TO_GRID); - } - - public static void setTokensStartFreesize(boolean flag) { - prefs.putBoolean(KEY_TOKENS_START_FREESIZE, flag); - } - - public static boolean getTokensStartFreesize() { - return prefs.getBoolean(KEY_TOKENS_START_FREESIZE, DEFAULT_TOKENS_START_FREESIZE); - } - - public static void setObjectsStartFreesize(boolean flag) { - prefs.putBoolean(KEY_OBJECTS_START_FREESIZE, flag); - } - - public static boolean getObjectsStartFreesize() { - return prefs.getBoolean(KEY_OBJECTS_START_FREESIZE, DEFAULT_OBJECTS_START_FREESIZE); - } - - public static void setBackgroundsStartSnapToGrid(boolean flag) { - prefs.putBoolean(KEY_BACKGROUNDS_START_SNAP_TO_GRID, flag); - } - - public static boolean getBackgroundsStartSnapToGrid() { - return prefs.getBoolean( - KEY_BACKGROUNDS_START_SNAP_TO_GRID, DEFAULT_BACKGROUNDS_START_SNAP_TO_GRID); - } - - public static void setBackgroundsStartFreesize(boolean flag) { - prefs.putBoolean(KEY_BACKGROUNDS_START_FREESIZE, flag); - } - - public static boolean getBackgroundsStartFreesize() { - return prefs.getBoolean(KEY_BACKGROUNDS_START_FREESIZE, DEFAULT_BACKGROUNDS_START_FREESIZE); - } - - public static String getDefaultGridType() { - return prefs.get(KEY_DEFAULT_GRID_TYPE, DEFAULT_DEFAULT_GRID_TYPE); - } - - public static void setDefaultGridType(String type) { - prefs.put(KEY_DEFAULT_GRID_TYPE, type); - } - - public static boolean getShowStatSheet() { - return prefs.getBoolean(KEY_SHOW_STAT_SHEET, DEFAULT_SHOW_STAT_SHEET); - } - - public static void setShowStatSheet(boolean show) { - prefs.putBoolean(KEY_SHOW_STAT_SHEET, show); - } - - public static boolean getShowPortrait() { - return prefs.getBoolean(KEY_SHOW_PORTRAIT, DEFAULT_SHOW_PORTRAIT); - } - - public static void setShowPortrait(boolean show) { - prefs.putBoolean(KEY_SHOW_PORTRAIT, show); - } - - public static boolean getShowStatSheetModifier() { - return prefs.getBoolean(KEY_SHOW_STAT_SHEET_MODIFIER, DEFAULT_SHOW_STAT_SHEET_MODIFIER); - } - - public static void setShowStatSheetModifier(boolean show) { - prefs.putBoolean(KEY_SHOW_STAT_SHEET_MODIFIER, show); - } - - public static boolean getForceFacingArrow() { - return prefs.getBoolean(KEY_FORCE_FACING_ARROW, DEFAULT_FORCE_FACING_ARROW); - } - - public static void setForceFacingArrow(boolean show) { - prefs.putBoolean(KEY_FORCE_FACING_ARROW, show); - } - - public static boolean getFitGMView() { - return prefs.getBoolean(KEY_FIT_GM_VIEW, DEFAULT_FIT_GM_VIEW); - } - - public static void setFitGMView(boolean fit) { - prefs.putBoolean(KEY_FIT_GM_VIEW, fit); - } - - public static String getDefaultUserName() { - return prefs.get(KEY_DEFAULT_USERNAME, DEFAULT_USERNAME); - } - - public static void setDefaultUserName(String uname) { - prefs.put(KEY_DEFAULT_USERNAME, uname); - } - - public static void setMovementMetric(WalkerMetric metric) { - prefs.put(KEY_MOVEMENT_METRIC, metric.name()); - } - - public static void setFrameRateCap(int cap) { - if (cap <= 0) { - // The provided value is invalid. Change to default instead. - cap = DEFAULT_FRAME_RATE_CAP; - } - prefs.putInt(KEY_FRAME_RATE_CAP, cap); - } - - public static int getFrameRateCap() { - int result = prefs.getInt(KEY_FRAME_RATE_CAP, DEFAULT_FRAME_RATE_CAP); - if (result <= 0) { - // An invalid value is stored. Fix that. - result = DEFAULT_FRAME_RATE_CAP; - setFrameRateCap(result); - } - return result; - } - - public static void setUpnpDiscoveryTimeout(int timeout) { - prefs.putInt(KEY_UPNP_DISCOVERY_TIMEOUT, timeout); - } - - public static int getUpnpDiscoveryTimeout() { - return prefs.getInt(KEY_UPNP_DISCOVERY_TIMEOUT, DEFAULT_UPNP_DISCOVERY_TIMEOUT); - } - - public static String getFileSyncPath() { - return prefs.get(KEY_FILE_SYNC_PATH, DEFAULT_FILE_SYNC_PATH); - } - - public static void setFileSyncPath(String path) { - prefs.put(KEY_FILE_SYNC_PATH, path); - } - - public static boolean getSkipAutoUpdate() { - return prefs.getBoolean(KEY_SKIP_AUTO_UPDATE, DEFAULT_SKIP_AUTO_UPDATE); - } - - public static void setSkipAutoUpdate(boolean value) { - prefs.putBoolean(KEY_SKIP_AUTO_UPDATE, value); - } - - public static String getSkipAutoUpdateRelease() { - return prefs.get(KEY_SKIP_AUTO_UPDATE_RELEASE, DEFAULT_SKIP_AUTO_UPDATE_RELEASE); - } - - public static void setSkipAutoUpdateRelease(String releaseId) { - prefs.put(KEY_SKIP_AUTO_UPDATE_RELEASE, releaseId); - } - - public static boolean getAllowExternalMacroAccess() { - return prefs.getBoolean(KEY_ALLOW_EXTERNAL_MACRO_ACCESS, DEFAULT_ALLOW_EXTERNAL_MACRO_ACCESS); - } - - public static void setAllowExternalMacroAccess(boolean value) { - prefs.putBoolean(KEY_ALLOW_EXTERNAL_MACRO_ACCESS, value); - } - - public static boolean getLoadMRUCampaignAtStart() { - return prefs.getBoolean(KEY_LOAD_MRU_CAMPAIGN_AT_START, DEFAULT_LOAD_MRU_CAMPAIGN_AT_START); - } - - public static void setLoadMRUCampaignAtStart(boolean value) { - prefs.putBoolean(KEY_LOAD_MRU_CAMPAIGN_AT_START, value); - } - - public static WalkerMetric getMovementMetric() { - WalkerMetric metric; - try { - metric = WalkerMetric.valueOf(prefs.get(KEY_MOVEMENT_METRIC, DEFAULT_MOVEMENT_METRIC.name())); - } catch (Exception exc) { - metric = DEFAULT_MOVEMENT_METRIC; - } - return metric; - } - - public static File getSaveDir() { - String filePath = prefs.get(KEY_SAVE_DIR, null); - return filePath != null ? new File(filePath) : new File(File.separator); - } - - public static File getSaveTokenDir() { - String filePath = prefs.get(KEY_SAVE_TOKEN_DIR, null); - return filePath != null ? new File(filePath) : getSaveDir(); - } - - public static void setTokenSaveDir(File file) { - prefs.put(KEY_SAVE_TOKEN_DIR, file.toString()); - } - - public static File getSaveMapDir() { - String filePath = prefs.get(KEY_SAVE_MAP_DIR, null); - return filePath != null ? new File(filePath) : getSaveDir(); - } - - public static void setSaveMapDir(File file) { - prefs.put(KEY_SAVE_MAP_DIR, file.toString()); - } - - public static void setLoadDir(File file) { - prefs.put(KEY_LOAD_DIR, file.toString()); - } - - public static File getLoadDir() { - String filePath = prefs.get(KEY_LOAD_DIR, null); - return filePath != null ? new File(filePath) : new File(File.separator); - } - - public static File getAddOnLoadDir() { - String filePath = prefs.get(KEY_ADD_ON_LOAD_DIR, null); - return filePath != null ? new File(filePath) : getSaveDir(); - } - - public static void setAddOnLoadDir(File file) { - prefs.put(KEY_ADD_ON_LOAD_DIR, file.toString()); - } - - private static final String INIT_SHOW_TOKENS = "initShowTokens"; - private static final boolean DEFAULT_INIT_SHOW_TOKENS = true; - - private static final String INIT_SHOW_TOKEN_STATES = "initShowTokenStates"; - private static final boolean DEFAULT_INIT_SHOW_TOKEN_STATES = true; - - private static final String INIT_SHOW_INITIATIVE = "initShowInitiative"; - private static final boolean DEFAULT_INIT_SHOW_INITIATIVE = true; - - private static final String INIT_SHOW_2ND_LINE = "initShow2ndLine"; - private static final boolean DEFAULT_INIT_SHOW_2ND_LINE = false; - - private static final String INIT_HIDE_NPCS = "initHideNpcs"; - private static final boolean DEFAULT_INIT_HIDE_NPCS = false; - - private static final String INIT_OWNER_PERMISSIONS = "initOwnerPermissions"; - private static final boolean DEFAULT_INIT_OWNER_PERMISSIONS = false; - - private static final String INIT_LOCK_MOVEMENT = "initLockMovement"; - private static final boolean DEFAULT_INIT_LOCK_MOVEMENT = false; - - public static boolean getInitShowTokens() { - return prefs.getBoolean(INIT_SHOW_TOKENS, DEFAULT_INIT_SHOW_TOKENS); - } - - public static void setInitShowTokens(boolean showTokens) { - prefs.putBoolean(INIT_SHOW_TOKENS, showTokens); - } - - public static boolean getInitShowTokenStates() { - return prefs.getBoolean(INIT_SHOW_TOKEN_STATES, DEFAULT_INIT_SHOW_TOKEN_STATES); - } - - public static void setInitShowTokenStates(boolean showTokenStates) { - prefs.putBoolean(INIT_SHOW_TOKEN_STATES, showTokenStates); - } - - public static boolean getInitShowInitiative() { - return prefs.getBoolean(INIT_SHOW_INITIATIVE, DEFAULT_INIT_SHOW_INITIATIVE); - } - - public static void setInitShowInitiative(boolean showInitiative) { - prefs.putBoolean(INIT_SHOW_INITIATIVE, showInitiative); - } - - public static boolean getInitShow2ndLine() { - return prefs.getBoolean(INIT_SHOW_2ND_LINE, DEFAULT_INIT_SHOW_2ND_LINE); - } - - public static void setInitShow2ndLine(boolean secondLine) { - prefs.putBoolean(INIT_SHOW_2ND_LINE, secondLine); - } - - public static boolean getInitHideNpcs() { - return prefs.getBoolean(INIT_HIDE_NPCS, DEFAULT_INIT_HIDE_NPCS); - } - - public static void setInitHideNpcs(boolean hideNpcs) { - prefs.putBoolean(INIT_HIDE_NPCS, hideNpcs); - } - - public static boolean getInitOwnerPermissions() { - return prefs.getBoolean(INIT_OWNER_PERMISSIONS, DEFAULT_INIT_OWNER_PERMISSIONS); - } - - public static void setInitOwnerPermissions(boolean ownerPermissions) { - prefs.putBoolean(INIT_OWNER_PERMISSIONS, ownerPermissions); - } - - public static boolean getInitLockMovement() { - return prefs.getBoolean(INIT_LOCK_MOVEMENT, DEFAULT_INIT_LOCK_MOVEMENT); - } - - public static void setInitLockMovement(boolean lockMovement) { - prefs.putBoolean(INIT_LOCK_MOVEMENT, lockMovement); - } - - public static boolean getChatNotificationShowBackground() { - // System.out.println("Getting Value:" + prefs.getBoolean(KEY_CHAT_NOTIFICATION_SHOW_BACKGROUND, - // DEFAULT_CHAT_NOTIFICATION_SHOW_BACKGROUND)); - return prefs.getBoolean( - KEY_CHAT_NOTIFICATION_SHOW_BACKGROUND, DEFAULT_CHAT_NOTIFICATION_SHOW_BACKGROUND); - } - - public static void setChatNotificationShowBackground(boolean flag) { - prefs.putBoolean(KEY_CHAT_NOTIFICATION_SHOW_BACKGROUND, flag); - } - - public static boolean isShowInitGainMessage() { - // KEY_SHOW_INIT_GAIN_MESSAGE - return prefs.getBoolean(KEY_SHOW_INIT_GAIN_MESSAGE, DEFAULT_SHOW_INIT_GAIN_MESSAGE); - } - - public static void setShowInitGainMessage(boolean flag) { - prefs.putBoolean(KEY_SHOW_INIT_GAIN_MESSAGE, flag); - } - - public static boolean isUsingAstarPathfinding() { - return prefs.getBoolean(KEY_USE_ASTAR_PATHFINDING, DEFAULT_USE_ASTAR_PATHFINDING); - } - - public static void setUseAstarPathfinding(boolean show) { - prefs.putBoolean(KEY_USE_ASTAR_PATHFINDING, show); - } - - public static boolean getVblBlocksMove() { - return prefs.getBoolean(KEY_VBL_BLOCKS_MOVE, DEFAULT_VBL_BLOCKS_MOVE); - } - - public static void setVblBlocksMove(boolean use) { - prefs.putBoolean(KEY_VBL_BLOCKS_MOVE, use); - } - - public static String getDefaultMacroEditorTheme() { - return prefs.get(MACRO_EDITOR_THEME, DEFAULT_MACRO_EDITOR_THEME); - } - - public static void setDefaultMacroEditorTheme(String type) { - prefs.put(MACRO_EDITOR_THEME, type); - } - - public static String getIconTheme() { - return prefs.get(ICON_THEME, DEFAULT_ICON_THEME); - } - - public static void setIconTheme(String theme) { - prefs.put(ICON_THEME, theme); - } - - public static void setWebEndPointPort(int value) { - prefs.putInt(KEY_WEB_END_POINT_PORT, value); - } - - public static int getWebEndPointPort() { - return prefs.getInt(KEY_WEB_END_POINT_PORT, DEFAULT_WEB_END_POINT); - } - - // Based off vision type enum in Zone.java, this could easily get tossed somewhere else if - // preferred. - public enum MapSortType { - DISPLAYNAME(), - GMNAME(); - - private final String displayName; - - MapSortType() { - displayName = I18N.getString("mapSortType." + name()); - } + MapSortType() { + displayName = I18N.getString("mapSortType." + name()); + } @Override public String toString() { @@ -1544,256 +496,6 @@ public String toString() { } } - /** - * Returns the background color to use for NPC Map Labels. - * - * @return the background color to use for NPC Map Labels. - */ - public static Color getNPCMapLabelBG() { - return new Color( - prefs.getInt(KEY_NPC_MAP_LABEL_BG_COLOR, DEFAULT_NPC_MAP_LABEL_BG_COLOR.getRGB()), true); - } - - /** - * Sets the background color to use for NPC Map Labels. - * - * @param color the background color to use for NPC Map Labels. - */ - public static void setNPCMapLabelBG(Color color) { - prefs.putInt(KEY_NPC_MAP_LABEL_BG_COLOR, color.getRGB()); - } - - /** - * Returns the border color to use for PC Map Labels. - * - * @return the border color to use for PC Map Labels. - */ - public static Color getPCMapLabelBorder() { - return new Color( - prefs.getInt(KEY_PC_MAP_LABEL_BORDER_COLOR, DEFAULT_PC_MAP_LABEL_BORDER_COLOR.getRGB()), - true); - } - - /** - * Sets the border color to use for PC Map Labels. - * - * @param color the border color to use for PC Map Labels. - */ - public static void setPCMapLabelBorder(Color color) { - prefs.putInt(KEY_PC_MAP_LABEL_BORDER_COLOR, color.getRGB()); - } - - /** - * Returns the foreground color to use for NPC Map Labels. - * - * @return the foreground color to use for NPC Map Labels. - */ - public static Color getNPCMapLabelFG() { - return new Color( - prefs.getInt(KEY_NPC_MAP_LABEL_FG_COLOR, DEFAULT_NPC_MAP_LABEL_FG_COLOR.getRGB()), true); - } - - /** - * Sets the foreground color to use for NPC Map Labels. - * - * @param color the foreground color to use for NPC Map Labels. - */ - public static void setNPCMapLabelFG(Color color) { - prefs.putInt(KEY_NPC_MAP_LABEL_FG_COLOR, color.getRGB()); - } - - /** - * Returns the border color to use for NPC Map Labels. - * - * @return the border color to use for NPC Map Labels. - */ - public static Color getNPCMapLabelBorder() { - return new Color( - prefs.getInt(KEY_NPC_MAP_LABEL_BORDER_COLOR, DEFAULT_NPC_MAP_LABEL_BORDER_COLOR.getRGB()), - true); - } - - /** - * Sets the border color to use for NPC Map Labels. - * - * @param color the border color to use for NPC Map Labels. - */ - public static void setNPCMapLabelBorder(Color color) { - prefs.putInt(KEY_NPC_MAP_LABEL_BORDER_COLOR, color.getRGB()); - } - - /** - * Returns the background color to use for PC Map Labels. - * - * @return the background color to use for PC Map Labels. - */ - public static Color getPCMapLabelBG() { - return new Color( - prefs.getInt(KEY_PC_MAP_LABEL_BG_COLOR, DEFAULT_PC_MAP_LABEL_BG_COLOR.getRGB()), true); - } - - /** - * Sets the background color to use for PC Map Labels. - * - * @param color the background color to use for PC Map Labels. - */ - public static void setPCMapLabelBG(Color color) { - prefs.putInt(KEY_PC_MAP_LABEL_BG_COLOR, color.getRGB()); - } - - /** - * Returns the foreground color to use for PC Map Labels. - * - * @return the foreground color to use for PC Map Labels. - */ - public static Color getPCMapLabelFG() { - return new Color( - prefs.getInt(KEY_PC_MAP_LABEL_FG_COLOR, DEFAULT_PC_MAP_LABEL_FG_COLOR.getRGB()), true); - } - - /** - * Sets the foreground color to use for PC Map Labels. - * - * @param color the foreground color to use for PC Map Labels. - */ - public static void setPCMapLabelFG(Color color) { - prefs.putInt(KEY_PC_MAP_LABEL_FG_COLOR, color.getRGB()); - } - - /** - * Returns the background color to use for Non-Visible Token Map Labels. - * - * @return the background color to use for Non-Visible Token Map Labels. - */ - public static Color getNonVisMapLabelBG() { - return new Color( - prefs.getInt(KEY_NONVIS_MAP_LABEL_BG_COLOR, DEFAULT_NONVIS_MAP_LABEL_BG_COLOR.getRGB()), - true); - } - - /** - * Sets the background color to use for Non-Visible Token Map Labels. - * - * @param color the background color to use for Non-Visible Token Map Labels. - */ - public static void setNonVisMapLabelBG(Color color) { - prefs.putInt(KEY_NONVIS_MAP_LABEL_BG_COLOR, color.getRGB()); - } - - /** - * Returns the foreground color to use for Non-Visible Token Map Labels. - * - * @return the foreground color to use for Non-Visible Token Map Labels. - */ - public static Color getNonVisMapLabelFG() { - return new Color( - prefs.getInt(KEY_NONVIS_MAP_LABEL_FG_COLOR, DEFAULT_NONVIS_MAP_LABEL_FG_COLOR.getRGB()), - true); - } - - /** - * Sets the foreground color to use for Non-Visible Token Map Labels. - * - * @param color the foreground color to use for Non-Visible Token Map Labels. - */ - public static void setNonVisMapLabelFG(Color color) { - prefs.putInt(KEY_NONVIS_MAP_LABEL_FG_COLOR, color.getRGB()); - } - - /** - * Returns the border color to use for Non-Visible Token Map Labels. - * - * @return the border color to use for Non-Visible Token Map Labels. - */ - public static Color getNonVisMapLabelBorder() { - return new Color( - prefs.getInt( - KEY_NONVIS_MAP_LABEL_BORDER_COLOR, DEFAULT_NONVIS_MAP_LABEL_BORDER_COLOR.getRGB()), - true); - } - - /** - * Sets the border color to use for Non-Visible Token Map Labels. - * - * @param color the border color to use for Non-Visible Token Map Labels. - */ - public static void setNonVisMapLabelBorder(Color color) { - prefs.putInt(KEY_NONVIS_MAP_LABEL_BORDER_COLOR, color.getRGB()); - } - - /** - * Returns the font size to use for Map Token Labels. - * - * @return the font size to use for Map Token Labels. - */ - public static int getMapLabelFontSize() { - return prefs.getInt(KEY_MAP_LABEL_FONT_SIZE, DEFAULT_MAP_LABEL_FONT_SIZE); - } - - /** - * Sets the font size to use for Map Token Labels. - * - * @param size the font size to use for Map Token Labels. - */ - public static void setMapLabelFontSize(int size) { - prefs.putInt(KEY_MAP_LABEL_FONT_SIZE, size); - } - - /** - * Gets the width of the border for token map labels. - * - * @return The width of the border for map labels. - */ - public static int getMapLabelBorderWidth() { - return prefs.getInt(KEY_MAP_LABEL_BORDER_WIDTH, DEFAULT_MAP_LABEL_BORDER_WIDTH); - } - - /** - * Sets the width of the border for token map labels. - * - * @param width the width of the border in pixels - */ - public static void setMapLabelBorderWidth(int width) { - prefs.putInt(KEY_MAP_LABEL_BORDER_WIDTH, width); - } - - /** - * Returns the value of the preference for the map label border arc. - * - * @return the value of the preference for the map label border arc - */ - public static int getMapLabelBorderArc() { - return prefs.getInt(KEY_MAP_LABEL_BORDER_ARC, DEFAULT_MAP_LABEL_BORDER_ARC); - } - - /** - * Sets the value of the preference for the map label border arc. - * - * @param arc the value of the preference for the map label border arc - */ - public static void setMapLabelBorderArc(int arc) { - prefs.putInt(KEY_MAP_LABEL_BORDER_ARC, arc); - } - - /** - * Returns the value of the preference "show map label border". The preference determines whether - * the border should be shown around the map label or not. - * - * @return {@code true} if the map label border should be shown, {@code false} otherwise. - */ - public static boolean getShowMapLabelBorder() { - return prefs.getBoolean(KEY_MAP_LABEL_SHOW_BORDER, DEFAULT_MAP_LABEL_SHOW_BORDER); - } - - /** - * Sets the preference for showing or hiding the border of map labels. - * - * @param show {@code true} to show the border, {@code false} to hide the border - */ - public static void setShowMapLabelBorder(boolean show) { - prefs.putBoolean(KEY_MAP_LABEL_SHOW_BORDER, show); - } - private interface Type { void set(Preferences node, String key, T value); diff --git a/src/main/java/net/rptools/maptool/client/AppState.java b/src/main/java/net/rptools/maptool/client/AppState.java index 81b9cc4531..e06e395879 100644 --- a/src/main/java/net/rptools/maptool/client/AppState.java +++ b/src/main/java/net/rptools/maptool/client/AppState.java @@ -50,8 +50,8 @@ public class AppState { private static PropertyChangeSupport changeSupport = new PropertyChangeSupport(AppState.class); static { - showLumensOverlay = AppPreferences.getLumensOverlayShowByDefault(); - showLights = AppPreferences.getLightsShowByDefault(); + showLumensOverlay = AppPreferences.lumensOverlayShowByDefault.get(); + showLights = AppPreferences.lightsShowByDefault.get(); } public static void addPropertyChangeListener(PropertyChangeListener listener) { diff --git a/src/main/java/net/rptools/maptool/client/AppUpdate.java b/src/main/java/net/rptools/maptool/client/AppUpdate.java index 8b1973cf63..27f515ce90 100644 --- a/src/main/java/net/rptools/maptool/client/AppUpdate.java +++ b/src/main/java/net/rptools/maptool/client/AppUpdate.java @@ -47,7 +47,9 @@ public class AppUpdate { */ public static boolean gitHubReleases() { // AppPreferences.setSkipAutoUpdate(false); // For testing only - if (AppPreferences.getSkipAutoUpdate()) return false; + if (AppPreferences.skipAutoUpdate.get()) { + return false; + } // Default for Linux? String DOWNLOAD_EXTENSION = ".deb"; @@ -75,7 +77,7 @@ public static boolean gitHubReleases() { return false; } - if (!AppPreferences.getSkipAutoUpdateRelease().equals(latestReleaseId) + if (!AppPreferences.skipAutoUpdateRelease.get().equals(latestReleaseId) && ModelVersionManager.isBefore(runningVersion, latestReleaseVersion)) { JsonArray releaseAssets = latestRelease.get("assets").getAsJsonArray(); @@ -240,9 +242,11 @@ private static boolean showMessage(String releaseId, String tagName) { options[1]); boolean dontAsk = dontAskCheckbox.isSelected(); - if (dontAsk) AppPreferences.setSkipAutoUpdate(true); + if (dontAsk) { + AppPreferences.skipAutoUpdate.set(true); + } - if (result == JOptionPane.CANCEL_OPTION) AppPreferences.setSkipAutoUpdateRelease(releaseId); + if (result == JOptionPane.CANCEL_OPTION) AppPreferences.skipAutoUpdateRelease.set(releaseId); return (result == JOptionPane.YES_OPTION); } diff --git a/src/main/java/net/rptools/maptool/client/AutoSaveManager.java b/src/main/java/net/rptools/maptool/client/AutoSaveManager.java index 5e7d5b33cb..650982b19f 100644 --- a/src/main/java/net/rptools/maptool/client/AutoSaveManager.java +++ b/src/main/java/net/rptools/maptool/client/AutoSaveManager.java @@ -65,7 +65,7 @@ private void execute() { private boolean executeAndContinue() { int interval = - AppPreferences.getAutoSaveIncrement() + AppPreferences.autoSaveIncrement.get() * 1000 * (DeveloperOptions.Toggle.AutoSaveMeasuredInSeconds.isEnabled() ? 1 : 60); diff --git a/src/main/java/net/rptools/maptool/client/ChatAutoSave.java b/src/main/java/net/rptools/maptool/client/ChatAutoSave.java index fe40fe5d9b..b6ca2610ef 100644 --- a/src/main/java/net/rptools/maptool/client/ChatAutoSave.java +++ b/src/main/java/net/rptools/maptool/client/ChatAutoSave.java @@ -20,6 +20,7 @@ import java.util.Date; import java.util.Timer; import java.util.TimerTask; +import java.util.concurrent.TimeUnit; import net.rptools.maptool.client.ui.commandpanel.CommandPanel; import net.rptools.maptool.language.I18N; import org.apache.logging.log4j.LogManager; @@ -29,34 +30,35 @@ * @author frank */ public class ChatAutoSave { - private static Logger log = LogManager.getLogger(ChatAutoSave.class); - private static final ChatAutoSave self = new ChatAutoSave(); + private static final Logger log = LogManager.getLogger(ChatAutoSave.class); private final Timer countdown; private TimerTask task; private long delay; - private static String chatlog = null; + private String chatlog = null; - private ChatAutoSave() { + public ChatAutoSave() { log.debug("Creating chat log autosave timer"); // $NON-NLS-1$ // Only way to set the delay is to call changeTimeout() - delay = 0; + this.delay = 0; countdown = new Timer(); } - private static TimerTask createTimer(final long timeout) { + private TimerTask createTimer(final long timeout) { TimerTask t = new TimerTask() { @Override public void run() { + log.info("Running the task"); + log.debug("Chat log autosave countdown complete from {}", timeout); // $NON-NLS-1$ if (chatlog == null) { - String filename = AppPreferences.getChatFilenameFormat(); + String filename = AppPreferences.chatFilenameFormat.get(); // FJE Ugly kludge to replace older default entry with newer default // TODO This is going into 1.3.b77 so remove it in 3-4 builds if ("chatlog.html".equals(filename)) { // $NON-NLS-1$ - AppPreferences.clearChatFilenameFormat(); - filename = AppPreferences.getChatFilenameFormat(); + AppPreferences.chatFilenameFormat.remove(); + filename = AppPreferences.chatFilenameFormat.get(); } chatlog = String.format(filename, new Date()).replace(':', '-'); } @@ -89,13 +91,9 @@ public void run() { return t; } - private static ChatAutoSave getInstance() { - return self; - } - - public static void changeTimeout(int timeout) { - getInstance().delay = timeout * 1000 * 60; - getInstance().start(); + public void setTimeout(int timeout) { + delay = TimeUnit.MINUTES.toMillis(timeout); + start(); } private void stop() { @@ -106,6 +104,8 @@ private void stop() { } private void start() { + log.info("Starting the countdown again"); + if (delay > 0) { stop(); task = createTimer(delay); diff --git a/src/main/java/net/rptools/maptool/client/ClientMessageHandler.java b/src/main/java/net/rptools/maptool/client/ClientMessageHandler.java index 9b66a3a4e0..34d8031a18 100644 --- a/src/main/java/net/rptools/maptool/client/ClientMessageHandler.java +++ b/src/main/java/net/rptools/maptool/client/ClientMessageHandler.java @@ -917,7 +917,7 @@ private void handle(EnforceZoneViewMsg msg) { if (renderer == null) { return; } - if (AppPreferences.getFitGMView()) { + if (AppPreferences.fitGmView.get()) { renderer.enforceView(x, y, scale, gmWidth, gmHeight); } else { renderer.setScale(scale); diff --git a/src/main/java/net/rptools/maptool/client/MapTool.java b/src/main/java/net/rptools/maptool/client/MapTool.java index 84a5326d42..441d8470c6 100644 --- a/src/main/java/net/rptools/maptool/client/MapTool.java +++ b/src/main/java/net/rptools/maptool/client/MapTool.java @@ -145,7 +145,7 @@ public class MapTool { // Set it to 500 (from 100) for now to support larger asset window previews // TODO: Add preferences option as well as add auto-purge after x days preferences private static final Dimension THUMBNAIL_SIZE = - new Dimension(AppPreferences.getThumbnailSize(), AppPreferences.getThumbnailSize()); + new Dimension(AppPreferences.thumbnailSize.get(), AppPreferences.thumbnailSize.get()); private static ThumbnailManager thumbnailManager; private static String version = "DEVELOPMENT"; @@ -168,6 +168,7 @@ public class MapTool { private static TaskBarFlasher taskbarFlasher; private static MapToolLineParser parser = new MapToolLineParser(); private static String lastWhisperer; + private static ChatAutoSave chatAutoSave; // Jamz: To support new command line parameters for multi-monitor support & enhanced PrintStream private static boolean debug = false; @@ -394,7 +395,7 @@ public static int confirmImpl(String title, int buttons, String message, Object. * @return true if the token should be deleted. */ public static boolean confirmTokenDelete() { - if (!AppPreferences.getTokensWarnWhenDeleted()) { + if (!AppPreferences.tokensWarnWhenDeleted.get()) { return true; } @@ -404,7 +405,7 @@ public static boolean confirmTokenDelete() { // "Yes, don't show again" Button if (val == 2) { showInformation("msg.confirm.deleteToken.removed"); - AppPreferences.setTokensWarnWhenDeleted(false); + AppPreferences.tokensWarnWhenDeleted.set(false); } // Any version of 'Yes' returns true, false otherwise return val == JOptionPane.YES_OPTION || val == 2; @@ -418,7 +419,7 @@ public static boolean confirmTokenDelete() { * @return true if the user clicks either Yes button, falsee otherwise. */ public static boolean confirmDrawDelete() { - if (!AppPreferences.getDrawWarnWhenDeleted()) { + if (!AppPreferences.drawingsWarnWhenDeleted.get()) { return true; } @@ -428,7 +429,7 @@ public static boolean confirmDrawDelete() { // "Yes, don't show again" Button if (val == JOptionPane.CANCEL_OPTION) { showInformation("msg.confirm.deleteDraw.removed"); - AppPreferences.setDrawWarnWhenDeleted(false); + AppPreferences.drawingsWarnWhenDeleted.set(false); } // Any version of 'Yes' returns true, otherwise false return val == JOptionPane.YES_OPTION || val == JOptionPane.CANCEL_OPTION; @@ -530,8 +531,8 @@ public static void showDocument(String url) { * @param eventId the eventId of the sound. */ public static void playSound(String eventId) { - if (AppPreferences.getPlaySystemSounds()) { - if (AppPreferences.getPlaySystemSoundsOnlyWhenNotFocused() && isInFocus()) { + if (AppPreferences.playSystemSounds.get()) { + if (AppPreferences.playSystemSoundsOnlyWhenNotFocused.get() && isInFocus()) { return; } SoundManager.playSoundEvent(eventId); @@ -686,9 +687,12 @@ private static void initialize() { } AppActions.updateActions(); - ToolTipManager.sharedInstance().setInitialDelay(AppPreferences.getToolTipInitialDelay()); - ToolTipManager.sharedInstance().setDismissDelay(AppPreferences.getToolTipDismissDelay()); - ChatAutoSave.changeTimeout(AppPreferences.getChatAutosaveTime()); + ToolTipManager.sharedInstance().setInitialDelay(AppPreferences.toolTipInitialDelay.get()); + ToolTipManager.sharedInstance().setDismissDelay(AppPreferences.toolTipDismissDelay.get()); + + chatAutoSave = new ChatAutoSave(); + chatAutoSave.setTimeout(AppPreferences.chatAutoSaveTimeInMinutes.get()); + AppPreferences.chatAutoSaveTimeInMinutes.onChange(chatAutoSave::setTimeout); // TODO: make this more formal when we switch to mina new ServerHeartBeatThread().start(); @@ -1276,7 +1280,7 @@ private static void postInitialize() { } } // alternately load MRU campaign if preference set - else if (AppPreferences.getLoadMRUCampaignAtStart()) { + else if (AppPreferences.loadMruCampaignAtStart.get()) { try { campaignFile = AppStatePersisted.getMruCampaigns().getFirst(); if (campaignFile.exists()) { @@ -1354,7 +1358,7 @@ public static String getLastWhisperer() { public static boolean useToolTipsForUnformatedRolls() { if (isPersonalServer() || getServerPolicy() == null) { - return AppPreferences.getUseToolTipForInlineRoll(); + return AppPreferences.useToolTipForInlineRoll.get(); } else { return getServerPolicy().getUseToolTipsForDefaultRollFormat(); } @@ -1653,7 +1657,7 @@ public static void main(String[] args) { factory.registerProtocol("lib", new LibraryURLStreamHandler()); // Syrinscape Protocols - if (AppPreferences.getSyrinscapeActive()) { + if (AppPreferences.syrinscapeActive.get()) { factory.registerProtocol("syrinscape-fantasy", new SyrinscapeURLStreamHandler()); factory.registerProtocol("syrinscape-sci-fi", new SyrinscapeURLStreamHandler()); factory.registerProtocol("syrinscape-boardgame", new SyrinscapeURLStreamHandler()); diff --git a/src/main/java/net/rptools/maptool/client/MapToolUtil.java b/src/main/java/net/rptools/maptool/client/MapToolUtil.java index 2c915970e1..fb8cec1852 100644 --- a/src/main/java/net/rptools/maptool/client/MapToolUtil.java +++ b/src/main/java/net/rptools/maptool/client/MapToolUtil.java @@ -137,7 +137,7 @@ public static String nextTokenId(Zone zone, Token token, boolean force) { String newName; Integer newNum = null; - if (isToken && AppPreferences.getNewTokenNaming().equals(Token.NAME_USE_CREATURE)) { + if (isToken && AppPreferences.newTokenNaming.get().equals(Token.NAME_USE_CREATURE)) { newName = I18N.getString("Token.name.creature"); } else if (!force) { return baseName; @@ -162,9 +162,12 @@ public static String nextTokenId(Zone zone, Token token, boolean force) { newName = baseName; } } - boolean random = (isToken && AppPreferences.getDuplicateTokenNumber().equals(Token.NUM_RANDOM)); - boolean addNumToGM = !AppPreferences.getTokenNumberDisplay().equals(Token.NUM_ON_NAME); - boolean addNumToName = !AppPreferences.getTokenNumberDisplay().equals(Token.NUM_ON_GM); + boolean random = + (isToken && AppPreferences.duplicateTokenNumber.get().equals(Token.NUM_RANDOM)); + + var tokenNumberDisplay = AppPreferences.tokenNumberDisplay.get(); + boolean addNumToGM = !tokenNumberDisplay.equals(Token.NUM_ON_NAME); + boolean addNumToName = !tokenNumberDisplay.equals(Token.NUM_ON_GM); /* * If the token already has a number suffix, if the preferences indicate that token numbering should be random and this token is on the Token layer, or if the token already exists somewhere on diff --git a/src/main/java/net/rptools/maptool/client/functions/ExportDataFunctions.java b/src/main/java/net/rptools/maptool/client/functions/ExportDataFunctions.java index 7ebc0e4c40..e96b3cbc6d 100644 --- a/src/main/java/net/rptools/maptool/client/functions/ExportDataFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/ExportDataFunctions.java @@ -57,7 +57,7 @@ public Object childEvaluate( if (!MapTool.getParser().isMacroTrusted()) throw new ParserException(I18N.getText("macro.function.general.noPerm", functionName)); - if (!AppPreferences.getAllowExternalMacroAccess()) + if (!AppPreferences.allowExternalMacroAccess.get()) throw new ParserException(I18N.getText("macro.function.general.accessDenied", functionName)); // New function to save data to an external file. diff --git a/src/main/java/net/rptools/maptool/client/functions/IsTrustedFunction.java b/src/main/java/net/rptools/maptool/client/functions/IsTrustedFunction.java index 176ed41ac1..b601d0db12 100644 --- a/src/main/java/net/rptools/maptool/client/functions/IsTrustedFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/IsTrustedFunction.java @@ -44,7 +44,7 @@ public Object childEvaluate( if (functionName.equalsIgnoreCase("isTrusted")) { return MapTool.getParser().isMacroTrusted() ? BigDecimal.ONE : BigDecimal.ZERO; } else if (functionName.equalsIgnoreCase("isExternalMacroAccessAllowed")) { - return AppPreferences.getAllowExternalMacroAccess() ? BigDecimal.ONE : BigDecimal.ZERO; + return AppPreferences.allowExternalMacroAccess.get() ? BigDecimal.ONE : BigDecimal.ZERO; } else if ("isGM".equalsIgnoreCase(functionName)) { if (parameters.isEmpty()) return MapTool.getPlayer().isGM() ? BigDecimal.ONE : BigDecimal.ZERO; diff --git a/src/main/java/net/rptools/maptool/client/functions/MapFunctions.java b/src/main/java/net/rptools/maptool/client/functions/MapFunctions.java index a448e822f0..eb10bd3645 100644 --- a/src/main/java/net/rptools/maptool/client/functions/MapFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/MapFunctions.java @@ -229,25 +229,25 @@ public Object childEvaluate( final var gridType = gridConfig.has("type") ? gridConfig.getAsJsonPrimitive("type").getAsString() - : AppPreferences.getDefaultGridType(); + : AppPreferences.defaultGridType.get(); final var grid = GridFactory.createGrid(gridType); final var gridColor = gridConfig.has("color") ? MapToolUtil.getColor(gridConfig.getAsJsonPrimitive("color").getAsString()) - : AppPreferences.getDefaultGridColor(); + : AppPreferences.defaultGridColor.get(); newMap.setGridColor(gridColor.getRGB()); final var gridUnitsPerCell = gridConfig.has("units per cell") ? gridConfig.getAsJsonPrimitive("units per cell").getAsDouble() - : AppPreferences.getDefaultUnitsPerCell(); + : AppPreferences.defaultUnitsPerCell.get(); newMap.setUnitsPerCell(gridUnitsPerCell); final var gridSize = gridConfig.has("size") ? gridConfig.getAsJsonPrimitive("size").getAsInt() - : AppPreferences.getDefaultGridSize(); + : AppPreferences.defaultGridSize.get(); grid.setSize(gridSize); final var gridOffsetX = diff --git a/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java b/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java index 57abffd710..36ba87b3b1 100644 --- a/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java @@ -79,7 +79,7 @@ public Object childEvaluate( throw new ParserException(I18N.getText("macro.function.general.noPerm", functionName)); } - if (!AppPreferences.getAllowExternalMacroAccess()) { + if (!AppPreferences.allowExternalMacroAccess.get()) { throw new ParserException(I18N.getText("macro.function.general.accessDenied", functionName)); } @@ -282,7 +282,7 @@ private Map> getHeaderMap(List parameters, int head * @throws ParserException */ private BigDecimal launchSyrinscape(String baseURL) throws ParserException { - if (!AppPreferences.getSyrinscapeActive()) { + if (!AppPreferences.syrinscapeActive.get()) { return BigDecimal.ZERO; } diff --git a/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java b/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java index dd19456e68..0dbc858f59 100644 --- a/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java @@ -68,7 +68,7 @@ public Object childEvaluate( int psize = args.size(); if (functionName.equalsIgnoreCase("playStream")) { FunctionUtil.checkNumberParam(functionName, args, 1, 5); - if (!AppPreferences.getPlayStreams()) return -1; // do nothing if disabled in preferences + if (!AppPreferences.playStreams.get()) return -1; // do nothing if disabled in preferences String strUri = convertToURI(args.get(0), true); Integer cycleCount = getCycleCount(functionName, args, 1); @@ -81,7 +81,7 @@ public Object childEvaluate( : BigDecimal.ZERO; } else if (functionName.equalsIgnoreCase("playClip")) { FunctionUtil.checkNumberParam(functionName, args, 1, 3); - if (!AppPreferences.getPlayStreams()) return -1; // do nothing if disabled in preferences + if (!AppPreferences.playStreams.get()) return -1; // do nothing if disabled in preferences String strUri = convertToURI(args.get(0), true); Integer cycleCount = getCycleCount(functionName, args, 1); diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenLocationFunctions.java b/src/main/java/net/rptools/maptool/client/functions/TokenLocationFunctions.java index 8051e24ce3..b6cc8697cc 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenLocationFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenLocationFunctions.java @@ -307,7 +307,7 @@ public double getDistance(Token source, Token target, boolean units, String metr if (wmetric == null && grid.useMetric()) wmetric = MapTool.isPersonalServer() - ? AppPreferences.getMovementMetric() + ? AppPreferences.movementMetric.get() : MapTool.getServerPolicy().getMovementMetric(); // explicitly find difference without walkers double curDist; diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenMoveFunctions.java b/src/main/java/net/rptools/maptool/client/functions/TokenMoveFunctions.java index b77c7a27e0..35c725b6d8 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenMoveFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenMoveFunctions.java @@ -479,7 +479,7 @@ private String getMovement( WalkerMetric metric = MapTool.isPersonalServer() - ? AppPreferences.getMovementMetric() + ? AppPreferences.movementMetric.get() : MapTool.getServerPolicy().getMovementMetric(); ZoneRenderer zr = MapTool.getFrame().getCurrentZoneRenderer(); diff --git a/src/main/java/net/rptools/maptool/client/functions/getInfoFunction.java b/src/main/java/net/rptools/maptool/client/functions/getInfoFunction.java index c691a0a845..d6ee017d86 100644 --- a/src/main/java/net/rptools/maptool/client/functions/getInfoFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/getInfoFunction.java @@ -217,17 +217,19 @@ private JsonObject getMapInfo() throws ParserException { private JsonObject getClientInfo() { JsonObject cinfo = new JsonObject(); - cinfo.addProperty("face edge", FunctionUtil.getDecimalForBoolean(AppPreferences.getFaceEdge())); cinfo.addProperty( - "face vertex", FunctionUtil.getDecimalForBoolean(AppPreferences.getFaceVertex())); - cinfo.addProperty("portrait size", AppPreferences.getPortraitSize()); - cinfo.addProperty("show portrait", AppPreferences.getShowPortrait()); - cinfo.addProperty("show stat sheet", AppPreferences.getShowStatSheet()); - cinfo.addProperty("file sync directory", AppPreferences.getFileSyncPath()); - cinfo.addProperty("show avatar in chat", AppPreferences.getShowAvatarInChat()); + "face edge", FunctionUtil.getDecimalForBoolean(AppPreferences.faceEdge.get())); cinfo.addProperty( - "suppress tooltips for macroLinks", AppPreferences.getSuppressToolTipsForMacroLinks()); - cinfo.addProperty("use tooltips for inline rolls", AppPreferences.getUseToolTipForInlineRoll()); + "face vertex", FunctionUtil.getDecimalForBoolean(AppPreferences.faceVertex.get())); + cinfo.addProperty("portrait size", AppPreferences.portraitSize.get()); + cinfo.addProperty("show portrait", AppPreferences.showPortrait.get()); + cinfo.addProperty("show stat sheet", AppPreferences.showStatSheet.get()); + cinfo.addProperty("file sync directory", AppPreferences.fileSyncPath.get()); + cinfo.addProperty("show avatar in chat", AppPreferences.showAvatarInChat.get()); + cinfo.addProperty( + "suppress tooltips for macroLinks", AppPreferences.suppressToolTipsForMacroLinks.get()); + cinfo.addProperty( + "use tooltips for inline rolls", AppPreferences.useToolTipForInlineRoll.get()); cinfo.addProperty("version", MapTool.getVersion()); cinfo.addProperty( "isFullScreen", FunctionUtil.getDecimalForBoolean(MapTool.getFrame().isFullScreen())); diff --git a/src/main/java/net/rptools/maptool/client/script/javascript/api/JSAPIClientInfo.java b/src/main/java/net/rptools/maptool/client/script/javascript/api/JSAPIClientInfo.java index 4d21e401bb..c831280703 100644 --- a/src/main/java/net/rptools/maptool/client/script/javascript/api/JSAPIClientInfo.java +++ b/src/main/java/net/rptools/maptool/client/script/javascript/api/JSAPIClientInfo.java @@ -31,22 +31,22 @@ public String serializeToString() { @HostAccess.Export public boolean faceEdge() { - return AppPreferences.getFaceEdge(); + return AppPreferences.faceEdge.get(); } @HostAccess.Export public boolean faceVertex() { - return AppPreferences.getFaceVertex(); + return AppPreferences.faceVertex.get(); } @HostAccess.Export public int portraitSize() { - return AppPreferences.getPortraitSize(); + return AppPreferences.portraitSize.get(); } @HostAccess.Export public boolean showStatSheet() { - return AppPreferences.getShowStatSheet(); + return AppPreferences.showStatSheet.get(); } @HostAccess.Export diff --git a/src/main/java/net/rptools/maptool/client/swing/ImagePanel.java b/src/main/java/net/rptools/maptool/client/swing/ImagePanel.java index bb7b191c38..acf3c97614 100644 --- a/src/main/java/net/rptools/maptool/client/swing/ImagePanel.java +++ b/src/main/java/net/rptools/maptool/client/swing/ImagePanel.java @@ -260,9 +260,9 @@ protected void paintComponent(Graphics gfx) { Dimension dim = constrainSize(image, gridSize); var savedRenderingHints = g.getRenderingHints(); if (dim.width < image.getWidth(null) || dim.height < image.getHeight(null)) { - AppPreferences.getRenderQuality().setShrinkRenderingHints(g); + AppPreferences.renderQuality.get().setShrinkRenderingHints(g); } else if (dim.width > image.getWidth(null) || dim.height > image.getHeight(null)) { - AppPreferences.getRenderQuality().setRenderingHints(g); + AppPreferences.renderQuality.get().setRenderingHints(g); } g.drawImage( image, diff --git a/src/main/java/net/rptools/maptool/client/swing/TooltipView.java b/src/main/java/net/rptools/maptool/client/swing/TooltipView.java index afecfce5f7..37bc501355 100644 --- a/src/main/java/net/rptools/maptool/client/swing/TooltipView.java +++ b/src/main/java/net/rptools/maptool/client/swing/TooltipView.java @@ -53,7 +53,7 @@ public String getToolTipText(float x, float y, Shape allocation) { if (href.startsWith("macro:")) { boolean isInsideChat = mlToolTips; - boolean allowToolTipToShow = !AppPreferences.getSuppressToolTipsForMacroLinks(); + boolean allowToolTipToShow = !AppPreferences.suppressToolTipsForMacroLinks.get(); if (isInsideChat && allowToolTipToShow) { return MacroLinkFunction.getInstance().macroLinkToolTip(href); } diff --git a/src/main/java/net/rptools/maptool/client/swing/label/FlatImageLabelFactory.java b/src/main/java/net/rptools/maptool/client/swing/label/FlatImageLabelFactory.java index 79cfe5f2e3..8e995ff648 100644 --- a/src/main/java/net/rptools/maptool/client/swing/label/FlatImageLabelFactory.java +++ b/src/main/java/net/rptools/maptool/client/swing/label/FlatImageLabelFactory.java @@ -39,20 +39,20 @@ public class FlatImageLabelFactory { /** Creates a new instance of the FlatImageLabelFactory class. */ public FlatImageLabelFactory() { - var npcBackground = AppPreferences.getNPCMapLabelBG(); - var npcForeground = AppPreferences.getNPCMapLabelFG(); - var npcBorder = AppPreferences.getNPCMapLabelBorder(); - var pcBackground = AppPreferences.getPCMapLabelBG(); - var pcForeground = AppPreferences.getPCMapLabelFG(); - var pcBorder = AppPreferences.getPCMapLabelBorder(); - var nonVisBackground = AppPreferences.getNonVisMapLabelBG(); - var nonVisForeground = AppPreferences.getNonVisMapLabelFG(); - var nonVisBorder = AppPreferences.getNonVisMapLabelBorder(); - int fontSize = AppPreferences.getMapLabelFontSize(); + var npcBackground = AppPreferences.npcMapLabelBackground.get(); + var npcForeground = AppPreferences.npcMapLabelForeground.get(); + var npcBorder = AppPreferences.npcMapLabelBorder.get(); + var pcBackground = AppPreferences.pcMapLabelBackground.get(); + var pcForeground = AppPreferences.pcMapLabelForeground.get(); + var pcBorder = AppPreferences.pcMapLabelBorder.get(); + var nonVisBackground = AppPreferences.nonVisibleTokenMapLabelBackground.get(); + var nonVisForeground = AppPreferences.nonVisibleTokenMapLabelForeground.get(); + var nonVisBorder = AppPreferences.nonVisibleTokenMapLabelBorder.get(); + int fontSize = AppPreferences.mapLabelFontSize.get(); var font = AppStyle.labelFont.deriveFont(AppStyle.labelFont.getStyle(), fontSize); - boolean showBorder = AppPreferences.getShowMapLabelBorder(); - int borderWidth = showBorder ? AppPreferences.getMapLabelBorderWidth() : 0; - int borderArc = AppPreferences.getMapLabelBorderArc(); + boolean showBorder = AppPreferences.mapLabelShowBorder.get(); + int borderWidth = showBorder ? AppPreferences.mapLabelBorderWidth.get() : 0; + int borderArc = AppPreferences.mapLabelBorderArc.get(); npcImageLabel = new FlatImageLabel( diff --git a/src/main/java/net/rptools/maptool/client/tool/AI_Tool.java b/src/main/java/net/rptools/maptool/client/tool/AI_Tool.java index 73b71af248..8824eeeef8 100644 --- a/src/main/java/net/rptools/maptool/client/tool/AI_Tool.java +++ b/src/main/java/net/rptools/maptool/client/tool/AI_Tool.java @@ -24,12 +24,12 @@ public class AI_Tool extends DefaultTool { public AI_Tool() { // Server policy is not available yet but that's ok, we have it saved as a preference which // is OK at this stage of initialization. - setSelected(AppPreferences.isUsingAstarPathfinding()); + setSelected(AppPreferences.pathfindingEnabled.get()); } @Override public void actionPerformed(ActionEvent e) { - AppPreferences.setUseAstarPathfinding(isSelected()); + AppPreferences.pathfindingEnabled.set(isSelected()); var client = MapTool.getClient(); var policy = client.getServerPolicy(); diff --git a/src/main/java/net/rptools/maptool/client/tool/AI_UseVblTool.java b/src/main/java/net/rptools/maptool/client/tool/AI_UseVblTool.java index 57f4ad6285..0978554f9f 100644 --- a/src/main/java/net/rptools/maptool/client/tool/AI_UseVblTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/AI_UseVblTool.java @@ -24,12 +24,12 @@ public class AI_UseVblTool extends DefaultTool { public AI_UseVblTool() { // Server policy is not available yet but that's ok, we have it saved as a preference which // is OK at this stage of initialization. - setSelected(AppPreferences.getVblBlocksMove()); + setSelected(AppPreferences.pathfindingBlockedByVbl.get()); } @Override public void actionPerformed(ActionEvent e) { - AppPreferences.setVblBlocksMove(isSelected()); + AppPreferences.pathfindingBlockedByVbl.set(isSelected()); var client = MapTool.getClient(); var policy = client.getServerPolicy(); @@ -57,7 +57,7 @@ public String getInstructions() { @Override public boolean isAvailable() { - return MapTool.getPlayer().isGM() && AppPreferences.isUsingAstarPathfinding(); + return MapTool.getPlayer().isGM() && AppPreferences.pathfindingEnabled.get(); } @Override diff --git a/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java b/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java index f0a2214acd..d7d060826d 100644 --- a/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/DefaultTool.java @@ -332,8 +332,8 @@ public void mouseWheelMoved(MouseWheelEvent e) { .getGrid() .nextFacing( facing, - AppPreferences.getFaceEdge(), - AppPreferences.getFaceVertex(), + AppPreferences.faceEdge.get(), + AppPreferences.faceVertex.get(), e.getWheelRotation() <= 0); } diff --git a/src/main/java/net/rptools/maptool/client/tool/FacingTool.java b/src/main/java/net/rptools/maptool/client/tool/FacingTool.java index f140b89f9d..3fbe20002d 100644 --- a/src/main/java/net/rptools/maptool/client/tool/FacingTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/FacingTool.java @@ -104,7 +104,8 @@ public void mouseMoved(MouseEvent e) { renderer .getZone() .getGrid() - .nearestFacing(degrees, AppPreferences.getFaceEdge(), AppPreferences.getFaceVertex()); + .nearestFacing( + degrees, AppPreferences.faceEdge.get(), AppPreferences.faceVertex.get()); } Area visibleArea = null; Set remoteSelected = new HashSet(); @@ -116,7 +117,7 @@ public void mouseMoved(MouseEvent e) { boolean noOwnerReveal; // if true, reveal FoW if token has no owners. if (MapTool.isPersonalServer()) { ownerReveal = - hasOwnerReveal = noOwnerReveal = AppPreferences.getAutoRevealVisionOnGMMovement(); + hasOwnerReveal = noOwnerReveal = AppPreferences.autoRevealVisionOnGMMovement.get(); } else { ownerReveal = MapTool.getServerPolicy().isAutoRevealOnMovement(); hasOwnerReveal = isGM && MapTool.getServerPolicy().isAutoRevealOnMovement(); 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 0d155b979d..67724666d5 100644 --- a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java @@ -771,7 +771,7 @@ public void mouseDragged(MouseEvent e) { selectedTokenSet, new ScreenPoint(dragStartX, dragStartY).convertToZone(renderer), false); - if (AppPreferences.getHideMousePointerWhileDragging()) { + if (AppPreferences.hideMousePointerWhileDragging.get()) { SwingUtil.hidePointer(renderer); } } @@ -1102,8 +1102,8 @@ private void handleKeyRotate(int direction, boolean freeRotate) { .getGrid() .nextFacing( facing, - AppPreferences.getFaceEdge(), - AppPreferences.getFaceVertex(), + AppPreferences.faceEdge.get(), + AppPreferences.faceVertex.get(), direction < 0); } MapTool.serverCommand().updateTokenProperty(token, Token.Update.setFacing, facing); @@ -1371,7 +1371,7 @@ public void paintOverlay(Graphics2D g) { Stroke stroke = g.getStroke(); g.setStroke(new BasicStroke(2)); - if (AppPreferences.getFillSelectionBox()) { + if (AppPreferences.fillSelectionBox.get()) { g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, .25f)); g.setPaint(AppStyle.selectionBoxFill); g.fillRoundRect( @@ -1403,8 +1403,9 @@ public void paintOverlay(Graphics2D g) { && tokenDragOp == null && AppUtil.tokenIsVisible( renderer.getZone(), tokenUnderMouse, new PlayerView(MapTool.getPlayer().getRole()))) { - if (AppPreferences.getPortraitSize() > 0 - && (SwingUtil.isShiftDown(keysDown) == AppPreferences.getShowStatSheetModifier()) + if (AppPreferences.portraitSize.get() > 0 + && (SwingUtil.isShiftDown(keysDown) + == AppPreferences.showStatSheetRequiresModifierKey.get()) && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet()) && (tokenOnStatSheet == null || !tokenOnStatSheet.equals(tokenUnderMouse) @@ -1413,7 +1414,7 @@ && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet()) BufferedImage image = null; Dimension imgSize = new Dimension(0, 0); - if (AppPreferences.getShowPortrait()) { + if (AppPreferences.showPortrait.get()) { // Portrait MD5Key portraitId = tokenUnderMouse.getPortraitImage() != null @@ -1434,7 +1435,7 @@ && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet()) imgSize = new Dimension(image.getWidth(), image.getHeight()); // Size - SwingUtil.constrainTo(imgSize, AppPreferences.getPortraitSize()); + SwingUtil.constrainTo(imgSize, AppPreferences.portraitSize.get()); } Dimension statSize = null; @@ -1454,7 +1455,7 @@ && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet()) Map propertyMap = new LinkedHashMap(); Map propertyLineCount = new LinkedHashMap(); LinkedList lineLayouts = new LinkedList(); - if (AppPreferences.getShowStatSheet() + if (AppPreferences.showStatSheet.get() && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet())) { CodeTimer.using( "statSheet", @@ -1640,7 +1641,7 @@ && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet())) { } // Draw the portrait - if (AppPreferences.getShowPortrait()) { + if (AppPreferences.showPortrait.get()) { Rectangle bounds = new Rectangle(lm, height - imgSize.height - bm, imgSize.width, imgSize.height); @@ -1649,7 +1650,7 @@ && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet())) { panelTexture, new Rectangle(0, 0, panelTexture.getWidth(), panelTexture.getHeight()))); statsG.fill(bounds); - AppPreferences.getRenderQuality().setShrinkRenderingHints(g); + AppPreferences.renderQuality.get().setShrinkRenderingHints(g); statsG.drawImage(image, bounds.x, bounds.y, imgSize.width, imgSize.height, this); AppStyle.miniMapBorder.paintAround(statsG, bounds); AppStyle.shadowBorder.paintWithin(statsG, bounds); @@ -1657,7 +1658,7 @@ && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet())) { // Label GraphicsUtil.drawBoxedString( statsG, tokenUnderMouse.getName(), bounds.width / 2 + lm, height - 15); - } else if (AppPreferences.getShowStatSheet() && statSize != null) { + } else if (AppPreferences.showStatSheet.get() && statSize != null) { // Label Rectangle bounds = new Rectangle( @@ -1865,7 +1866,7 @@ public void dragTo(int mouseX, int mouseY) { var grid = renderer.getZone().getGrid(); if (tokenBeingDragged.isSnapToGrid() && grid.getCapabilities().isSnapToGridSupported() - && AppPreferences.getTokensSnapWhileDragging()) { + && AppPreferences.tokensSnapWhileDragging.get()) { // Snap to grid point. zonePoint = grid.convert(grid.convert(zonePoint)); @@ -1967,7 +1968,7 @@ private void exposeFoW(ZonePoint p) { if (MapTool.isPersonalServer()) { ownerReveal = - hasOwnerReveal = noOwnerReveal = AppPreferences.getAutoRevealVisionOnGMMovement(); + hasOwnerReveal = noOwnerReveal = AppPreferences.autoRevealVisionOnGMMovement.get(); } else { ownerReveal = MapTool.getServerPolicy().isAutoRevealOnMovement(); hasOwnerReveal = isGM && MapTool.getServerPolicy().isAutoRevealOnMovement(); diff --git a/src/main/java/net/rptools/maptool/client/tool/StampTool.java b/src/main/java/net/rptools/maptool/client/tool/StampTool.java index 1826b03966..03da47ed7b 100644 --- a/src/main/java/net/rptools/maptool/client/tool/StampTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/StampTool.java @@ -970,7 +970,7 @@ public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { Stroke stroke = g.getStroke(); g.setStroke(new BasicStroke(2)); - if (AppPreferences.getFillSelectionBox()) { + if (AppPreferences.fillSelectionBox.get()) { Composite composite = g.getComposite(); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, .25f)); g.setPaint(AppStyle.selectionBoxFill); diff --git a/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java b/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java index 76fb3170a4..a052edcb30 100644 --- a/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java +++ b/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java @@ -664,7 +664,7 @@ public void actionPerformed(ActionEvent e) { } } if (saveDirectory != null) { - AppPreferences.setTokenSaveDir(saveDirectory); + AppPreferences.tokenSaveDirectory.set(saveDirectory); } } } diff --git a/src/main/java/net/rptools/maptool/client/ui/AssetViewerDialog.java b/src/main/java/net/rptools/maptool/client/ui/AssetViewerDialog.java index 006925cd51..fea5ac8995 100644 --- a/src/main/java/net/rptools/maptool/client/ui/AssetViewerDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/AssetViewerDialog.java @@ -167,7 +167,7 @@ public void paintComponent(Graphics g) { SwingUtil.constrainTo(imgSize, size.width, size.height); Object oldHint = g2d.getRenderingHint(RenderingHints.KEY_RENDERING); - AppPreferences.getRenderQuality().setShrinkRenderingHints(g2d); + AppPreferences.renderQuality.get().setShrinkRenderingHints(g2d); g.drawImage(image, 0, 0, imgSize.width, imgSize.height, this); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, oldHint); diff --git a/src/main/java/net/rptools/maptool/client/ui/ChatTypingNotification.java b/src/main/java/net/rptools/maptool/client/ui/ChatTypingNotification.java index 14bc998e93..7c5071f59f 100644 --- a/src/main/java/net/rptools/maptool/client/ui/ChatTypingNotification.java +++ b/src/main/java/net/rptools/maptool/client/ui/ChatTypingNotification.java @@ -44,14 +44,14 @@ public class ChatTypingNotification extends JPanel { @Override protected void paintComponent(Graphics g) { // System.out.println("Chat panel is painting itself..."); - if (AppPreferences.getTypingNotificationDuration() == 0) { + if (AppPreferences.typingNotificationDurationInSeconds.get() == 0) { return; } LinkedMap chatTypers = MapTool.getFrame().getChatNotificationTimers().getChatTypers(); if (chatTypers == null || chatTypers.isEmpty()) { return; } - boolean showBackground = AppPreferences.getChatNotificationShowBackground(); + boolean showBackground = AppPreferences.chatNotificationBackground.get(); Graphics2D statsG = (Graphics2D) g.create(); diff --git a/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java b/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java index 607c125718..131a366ed4 100644 --- a/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java +++ b/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java @@ -307,7 +307,7 @@ public class ChatNotificationTimers { private final LinkedMap chatTypingNotificationTimers; public synchronized void setChatTyper(final String playerName) { - if (AppPreferences.getTypingNotificationDuration() == 0) { + if (AppPreferences.typingNotificationDurationInSeconds.get() == 0) { turnOffUpdates(); chatTypingNotificationTimers.clear(); } else { @@ -356,8 +356,9 @@ public MapToolFrame(JMenuBar menuBar) { setFocusTraversalPolicy(new MapToolFocusTraversalPolicy()); setIconImage(RessourceManager.getImage(Images.MAPTOOL_LOGO_MINI)); - // Notify duration - initializeNotifyDuration(); + chatNotifyDuration = AppPreferences.typingNotificationDurationInSeconds.get(); + AppPreferences.typingNotificationDurationInSeconds.onChange( + value -> chatNotifyDuration = value); // Components glassPane = new GlassPane(); @@ -462,7 +463,7 @@ public MapToolFrame(JMenuBar menuBar) { new WindowPreferences(AppConstants.APP_NAME, "mainFrame", this); chatTyperTimers = new ChatNotificationTimers(); chatTimer = getChatTimer(); - setChatTypingLabelColor(AppPreferences.getChatNotificationColor()); + setChatTypingLabelColor(AppPreferences.chatNotificationColor.get()); } public ChatNotificationTimers getChatNotificationTimers() { @@ -852,7 +853,7 @@ public FileFilter getDungeonDraftFilter() { public JFileChooser getLoadPropsFileChooser() { if (loadPropsFileChooser == null) { loadPropsFileChooser = new JFileChooser(); - loadPropsFileChooser.setCurrentDirectory(AppPreferences.getLoadDir()); + loadPropsFileChooser.setCurrentDirectory(AppPreferences.loadDirectory.get()); loadPropsFileChooser.addChoosableFileFilter(propertiesFilter); loadPropsFileChooser.setDialogTitle(I18N.getText("msg.title.importProperties")); } @@ -863,7 +864,7 @@ public JFileChooser getLoadPropsFileChooser() { public JFileChooser getLoadFileChooser() { if (loadFileChooser == null) { loadFileChooser = new JFileChooser(); - loadFileChooser.setCurrentDirectory(AppPreferences.getLoadDir()); + loadFileChooser.setCurrentDirectory(AppPreferences.loadDirectory.get()); } return loadFileChooser; } @@ -871,7 +872,7 @@ public JFileChooser getLoadFileChooser() { public JFileChooser getSaveCmpgnFileChooser() { if (saveCmpgnFileChooser == null) { saveCmpgnFileChooser = new JFileChooser(); - saveCmpgnFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); + saveCmpgnFileChooser.setCurrentDirectory(AppPreferences.saveDirectory.get()); saveCmpgnFileChooser.addChoosableFileFilter(campaignFilter); saveCmpgnFileChooser.setDialogTitle(I18N.getText("msg.title.saveCampaign")); } @@ -882,7 +883,7 @@ public JFileChooser getSaveCmpgnFileChooser() { public JFileChooser getSavePropsFileChooser() { if (savePropsFileChooser == null) { savePropsFileChooser = new JFileChooser(); - savePropsFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); + savePropsFileChooser.setCurrentDirectory(AppPreferences.saveDirectory.get()); savePropsFileChooser.addChoosableFileFilter(propertiesFilter); savePropsFileChooser.setDialogTitle(I18N.getText("msg.title.exportProperties")); } @@ -893,7 +894,7 @@ public JFileChooser getSavePropsFileChooser() { public JFileChooser getSaveTokenFileChooser() { if (saveTokenFileChooser == null) { saveTokenFileChooser = new JFileChooser(); - saveTokenFileChooser.setCurrentDirectory(AppPreferences.getSaveTokenDir()); + saveTokenFileChooser.setCurrentDirectory(AppPreferences.tokenSaveDirectory.get()); } return saveTokenFileChooser; } @@ -901,7 +902,7 @@ public JFileChooser getSaveTokenFileChooser() { public JFileChooser getSaveMapFileChooser() { if (saveMapFileChooser == null) { saveMapFileChooser = new JFileChooser(); - saveMapFileChooser.setCurrentDirectory(AppPreferences.getSaveMapDir()); + saveMapFileChooser.setCurrentDirectory(AppPreferences.mapSaveDirectory.get()); } return saveMapFileChooser; } @@ -909,7 +910,7 @@ public JFileChooser getSaveMapFileChooser() { public JFileChooser getSaveFileChooser() { if (saveFileChooser == null) { saveFileChooser = new JFileChooser(); - saveFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); + saveFileChooser.setCurrentDirectory(AppPreferences.saveDirectory.get()); } return saveFileChooser; } @@ -1080,14 +1081,6 @@ public void setChatTypingLabelColor(Color color) { } } - public void setChatNotifyDuration(int duration) { - chatNotifyDuration = duration; - } - - private void initializeNotifyDuration() { - chatNotifyDuration = AppPreferences.getTypingNotificationDuration(); - } - public JLabel getChatActionLabel() { if (chatActionLabel == null) { chatActionLabel = new JLabel(RessourceManager.getSmallIcon(Icons.CHAT_NOTIFICATION)); @@ -1930,7 +1923,7 @@ public boolean confirmClose() { } public void closingMaintenance() { - if (AppPreferences.getSaveReminder() && MapTool.isCampaignDirty()) { + if (AppPreferences.saveReminder.get() && MapTool.isCampaignDirty()) { if (MapTool.getPlayer().isGM()) { int result = MapTool.confirmImpl( @@ -2134,7 +2127,7 @@ public InitiativePanel getInitiativePanel() { public JFileChooser getSaveMacroFileChooser() { if (saveMacroFileChooser == null) { saveMacroFileChooser = new JFileChooser(); - saveMacroFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); + saveMacroFileChooser.setCurrentDirectory(AppPreferences.saveDirectory.get()); saveMacroFileChooser.addChoosableFileFilter(macroFilter); saveMacroFileChooser.setDialogTitle(I18N.getText("msg.title.exportMacro")); } @@ -2145,7 +2138,7 @@ public JFileChooser getSaveMacroFileChooser() { public JFileChooser getSaveMacroSetFileChooser() { if (saveMacroSetFileChooser == null) { saveMacroSetFileChooser = new JFileChooser(); - saveMacroSetFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); + saveMacroSetFileChooser.setCurrentDirectory(AppPreferences.saveDirectory.get()); saveMacroSetFileChooser.addChoosableFileFilter(macroSetFilter); saveMacroSetFileChooser.setDialogTitle(I18N.getText("msg.title.exportMacroSet")); } @@ -2159,7 +2152,7 @@ public JFileChooser getSaveMacroSetFileChooser() { public JFileChooser getLoadMacroFileChooser() { if (loadMacroFileChooser == null) { loadMacroFileChooser = new JFileChooser(); - loadMacroFileChooser.setCurrentDirectory(AppPreferences.getLoadDir()); + loadMacroFileChooser.setCurrentDirectory(AppPreferences.loadDirectory.get()); loadMacroFileChooser.addChoosableFileFilter(macroFilter); loadMacroFileChooser.setDialogTitle(I18N.getText("msg.title.importMacro")); } @@ -2170,7 +2163,7 @@ public JFileChooser getLoadMacroFileChooser() { public JFileChooser getLoadMacroSetFileChooser() { if (loadMacroSetFileChooser == null) { loadMacroSetFileChooser = new JFileChooser(); - loadMacroSetFileChooser.setCurrentDirectory(AppPreferences.getLoadDir()); + loadMacroSetFileChooser.setCurrentDirectory(AppPreferences.loadDirectory.get()); loadMacroSetFileChooser.addChoosableFileFilter(macroSetFilter); loadMacroSetFileChooser.setDialogTitle(I18N.getText("msg.title.importMacroSet")); } @@ -2186,7 +2179,7 @@ public JFileChooser getLoadMacroSetFileChooser() { public JFileChooser getSaveTableFileChooser() { if (saveTableFileChooser == null) { saveTableFileChooser = new JFileChooser(); - saveTableFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); + saveTableFileChooser.setCurrentDirectory(AppPreferences.saveDirectory.get()); saveTableFileChooser.addChoosableFileFilter(tableFilter); saveTableFileChooser.setDialogTitle(I18N.getText("Label.table.export")); } @@ -2200,7 +2193,7 @@ public JFileChooser getSaveTableFileChooser() { public JFileChooser getLoadTableFileChooser() { if (loadTableFileChooser == null) { loadTableFileChooser = new JFileChooser(); - loadTableFileChooser.setCurrentDirectory(AppPreferences.getLoadDir()); + loadTableFileChooser.setCurrentDirectory(AppPreferences.loadDirectory.get()); loadTableFileChooser.addChoosableFileFilter(tableFilter); loadTableFileChooser.setDialogTitle(I18N.getText("Label.table.import")); } diff --git a/src/main/java/net/rptools/maptool/client/ui/PreviewPanelFileChooser.java b/src/main/java/net/rptools/maptool/client/ui/PreviewPanelFileChooser.java index 7b8989867d..d1c5c1c373 100644 --- a/src/main/java/net/rptools/maptool/client/ui/PreviewPanelFileChooser.java +++ b/src/main/java/net/rptools/maptool/client/ui/PreviewPanelFileChooser.java @@ -41,7 +41,7 @@ public class PreviewPanelFileChooser extends JFileChooser { new ThumbnailManager(AppUtil.getAppHome("previewPanelThumbs"), new Dimension(150, 150)); public PreviewPanelFileChooser() { - this.setCurrentDirectory(AppPreferences.getLoadDir()); + this.setCurrentDirectory(AppPreferences.loadDirectory.get()); this.setAccessory(getPreviewWrapperPanel()); this.addPropertyChangeListener( PreviewPanelFileChooser.SELECTED_FILE_CHANGED_PROPERTY, new FileSystemSelectionHandler()); diff --git a/src/main/java/net/rptools/maptool/client/ui/ZoneSelectionPopup.java b/src/main/java/net/rptools/maptool/client/ui/ZoneSelectionPopup.java index 5fd63edbc0..397060f969 100644 --- a/src/main/java/net/rptools/maptool/client/ui/ZoneSelectionPopup.java +++ b/src/main/java/net/rptools/maptool/client/ui/ZoneSelectionPopup.java @@ -54,7 +54,7 @@ private JMenuItem createEntries() { rendererList.removeIf(renderer -> !renderer.getZone().isVisible()); } - if (AppPreferences.getMapSortType().equals(AppPreferences.MapSortType.GMNAME)) + if (AppPreferences.mapSortType.get().equals(AppPreferences.MapSortType.GMNAME)) rendererList.sort( (o1, o2) -> { String name1 = o1.getZone().getName(); diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenBarController.java b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenBarController.java index 9e1350cbe2..67d1218a72 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenBarController.java +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenBarController.java @@ -381,7 +381,7 @@ public void actionPerformed(ActionEvent e) { } // endif ((JScrollPane) formPanel.getComponent("tokenBarImagesScroll")) .scrollRectToVisible(imageList.getCellBounds(imageSelected, imageSelected)); - AppPreferences.setLoadDir(imageFile.getParentFile()); + AppPreferences.loadDirectory.set(imageFile.getParentFile()); changedUpdate(null); } // endif @@ -399,7 +399,7 @@ public void actionPerformed(ActionEvent e) { imageModel.set( imageSelected, TokenStatesController.loadAsssetFile(imageFile.getAbsolutePath(), formPanel)); - AppPreferences.setLoadDir(imageFile.getParentFile()); + AppPreferences.loadDirectory.set(imageFile.getParentFile()); } // endif // Delete an image in the list diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenStatesController.java b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenStatesController.java index 166a82603b..661b4ead0e 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenStatesController.java +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenStatesController.java @@ -345,7 +345,7 @@ public void actionPerformed(ActionEvent e) { || !imageFile.exists() || !imageFile.canRead()) return; formPanel.getTextComponent(IMAGE).setText(imageFile.getPath()); - AppPreferences.setLoadDir(imageFile.getParentFile()); + AppPreferences.loadDirectory.set(imageFile.getParentFile()); } // endif // Change the enabled data components. diff --git a/src/main/java/net/rptools/maptool/client/ui/chat/SmileyChatTranslationRuleGroup.java b/src/main/java/net/rptools/maptool/client/ui/chat/SmileyChatTranslationRuleGroup.java index ecbb78418f..702f0869b5 100644 --- a/src/main/java/net/rptools/maptool/client/ui/chat/SmileyChatTranslationRuleGroup.java +++ b/src/main/java/net/rptools/maptool/client/ui/chat/SmileyChatTranslationRuleGroup.java @@ -46,7 +46,7 @@ public JPopupMenu getEmotePopup() { @Override public boolean isEnabled() { - return AppPreferences.getShowSmilies(); + return AppPreferences.showSmilies.get(); } private void initSmilies() { diff --git a/src/main/java/net/rptools/maptool/client/ui/commandpanel/CommandPanel.java b/src/main/java/net/rptools/maptool/client/ui/commandpanel/CommandPanel.java index ebfec0569e..6fc3e87dec 100644 --- a/src/main/java/net/rptools/maptool/client/ui/commandpanel/CommandPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/commandpanel/CommandPanel.java @@ -295,7 +295,7 @@ void onPreferencesChanged(PreferencesChanged event) { // Resize on demand if (commandTextArea != null) { commandTextArea.setFont( - commandTextArea.getFont().deriveFont((float) AppPreferences.getFontSize())); + commandTextArea.getFont().deriveFont((float) AppPreferences.fontSize.get())); doLayout(); } @@ -606,7 +606,7 @@ protected void paintComponent(Graphics g) { }; commandTextArea.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5)); commandTextArea.setPreferredSize(new Dimension(50, 40)); // XXX should be resizable - commandTextArea.setFont(new Font("sans-serif", 0, AppPreferences.getFontSize())); + commandTextArea.setFont(new Font("sans-serif", 0, AppPreferences.fontSize.get())); if (!ThemeSupport.shouldUseThemeColorsForChat()) { commandTextArea.setBackground(Color.WHITE); commandTextArea.setForeground(Color.BLACK); @@ -837,7 +837,7 @@ public static class TextColorWell extends JPanel { private static final long serialVersionUID = -9006587537198176935L; // Set the Color from the saved chat color from AppPreferences - private Color color = AppPreferences.getChatColor(); + private Color color = AppPreferences.chatColor.get(); public TextColorWell() { setMinimumSize(new Dimension(15, 15)); @@ -862,7 +862,7 @@ public void mouseClicked(MouseEvent e) { public void setColor(Color newColor) { color = newColor; repaint(); - AppPreferences.setChatColor(color); // Set the Chat Color in AppPreferences + AppPreferences.chatColor.set(color); // Set the Chat Color in AppPreferences } public Color getColor() { @@ -919,7 +919,7 @@ protected void paintComponent(Graphics g) { Dimension imgSize = new Dimension(image.getWidth(null), image.getHeight(null)); SwingUtil.constrainTo(imgSize, size.width - PADDING * 2, size.height - PADDING * 2); - AppPreferences.getRenderQuality().setShrinkRenderingHints((Graphics2D) g); + AppPreferences.renderQuality.get().setShrinkRenderingHints((Graphics2D) g); g.drawImage( image, (size.width - imgSize.width) / 2, diff --git a/src/main/java/net/rptools/maptool/client/ui/commandpanel/MessagePanel.java b/src/main/java/net/rptools/maptool/client/ui/commandpanel/MessagePanel.java index 79ac8603e5..fe794dfd55 100644 --- a/src/main/java/net/rptools/maptool/client/ui/commandpanel/MessagePanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/commandpanel/MessagePanel.java @@ -166,14 +166,14 @@ public void refreshRenderer() { + "; color: " + fgColour + " ; font-family: sans-serif; font-size: " - + AppPreferences.getFontSize() + + AppPreferences.fontSize.get() + "pt}"; style.addRule(mainCss); style.addRule("div {margin-bottom: 5px}"); style.addRule(".roll {background:#efefef}"); setTrustedMacroPrefixColors( - AppPreferences.getTrustedPrefixFG(), AppPreferences.getTrustedPrefixBG()); + AppPreferences.trustedPrefixForeground.get(), AppPreferences.trustedPrefixBackground.get()); var css = MessageUtil.getMessageCss(); style.addRule(css); repaint(); diff --git a/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLOverlayManager.java b/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLOverlayManager.java index 91cdd59368..c1b0f3e020 100644 --- a/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLOverlayManager.java +++ b/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLOverlayManager.java @@ -161,7 +161,7 @@ private void updateOverlayCursor(javafx.scene.Cursor newCursor) { */ @Override String getCSSRule() { - return String.format(CSS_BODY, AppPreferences.getFontSize()) + return String.format(CSS_BODY, AppPreferences.fontSize.get()) + CSS_SPAN + CSS_DIV + CSS_POINTERMAP; diff --git a/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLPane.java b/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLPane.java index de780c04d2..2a57a9e5fe 100644 --- a/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLPane.java +++ b/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLPane.java @@ -91,7 +91,7 @@ public HTMLPane() { * @return the rule for the body tag */ public String getRuleBody() { - return String.format(CSS_RULE_BODY, AppPreferences.getFontSize()); + return String.format(CSS_RULE_BODY, AppPreferences.fontSize.get()); } public void addActionListener(ActionListener listener) { diff --git a/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLWebViewManager.java b/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLWebViewManager.java index 837c3c620d..7f4becc55a 100644 --- a/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLWebViewManager.java +++ b/src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLWebViewManager.java @@ -421,7 +421,7 @@ private static void showError(WebErrorEvent event) { } String getCSSRule() { - return String.format(CSS_BODY, AppPreferences.getFontSize()) + CSS_SPAN + CSS_DIV; + return String.format(CSS_BODY, AppPreferences.fontSize.get()) + CSS_SPAN + CSS_DIV; } /** diff --git a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/buttons/MacroButtonPrefs.java b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/buttons/MacroButtonPrefs.java index 08f1a63733..6cbf21ba61 100644 --- a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/buttons/MacroButtonPrefs.java +++ b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/buttons/MacroButtonPrefs.java @@ -202,7 +202,7 @@ public static List getButtonProperties() { String maxWidth = buttonPref.get(PREF_MAX_WIDTH, ""); boolean allowPlayerEdits = buttonPref.getBoolean( - PREF_ALLOW_PLAYER_EDITS, AppPreferences.getAllowPlayerMacroEditsDefault()); + PREF_ALLOW_PLAYER_EDITS, AppPreferences.allowPlayerMacroEditsDefault.get()); String toolTip = buttonPref.get(PREF_TOOLTIP, ""); boolean displayHotKey = buttonPref.getBoolean(PREF_DISPLAY_HOT_KEY, true); @@ -245,7 +245,7 @@ public static List getButtonProperties() { String maxWidth = buttonPref.get(PREF_MAX_WIDTH, ""); boolean allowPlayerEdits = buttonPref.getBoolean( - PREF_ALLOW_PLAYER_EDITS, AppPreferences.getAllowPlayerMacroEditsDefault()); + PREF_ALLOW_PLAYER_EDITS, AppPreferences.allowPlayerMacroEditsDefault.get()); String hotKey = buttonPref.get(PREF_HOTKEY_KEY, MacroButtonHotKeyManager.HOTKEYS[0]); String toolTip = buttonPref.get(PREF_TOOLTIP, ""); boolean displayHotKey = buttonPref.getBoolean(PREF_DISPLAY_HOT_KEY, true); diff --git a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/dialog/MacroEditorDialog.java b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/dialog/MacroEditorDialog.java index 79d61f96d2..87e95c434f 100644 --- a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/dialog/MacroEditorDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/dialog/MacroEditorDialog.java @@ -493,7 +493,7 @@ private void initCommandTextArea() { // Set the color style via Theme try { File themeFile = - new File(AppConstants.THEMES_DIR, AppPreferences.getDefaultMacroEditorTheme() + ".xml"); + new File(AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); Theme theme = Theme.load(new FileInputStream(themeFile)); theme.apply(macroEditorRSyntaxTextArea); theme.apply(getToolTipTextField()); diff --git a/src/main/java/net/rptools/maptool/client/ui/mappropertiesdialog/MapPropertiesDialog.java b/src/main/java/net/rptools/maptool/client/ui/mappropertiesdialog/MapPropertiesDialog.java index 786acb4f48..43b2394e45 100644 --- a/src/main/java/net/rptools/maptool/client/ui/mappropertiesdialog/MapPropertiesDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/mappropertiesdialog/MapPropertiesDialog.java @@ -328,29 +328,29 @@ private void copyUIToZone() { } private void initIsometricRadio() { - getIsometricRadio().setSelected(GridFactory.isIsometric(AppPreferences.getDefaultGridType())); + getIsometricRadio().setSelected(GridFactory.isIsometric(AppPreferences.defaultGridType.get())); getIsometricIcon().setIcon(RessourceManager.getSmallIcon(Icons.GRID_ISOMETRIC)); } private void initHexHoriRadio() { getHexHorizontalRadio() - .setSelected(GridFactory.isHexHorizontal(AppPreferences.getDefaultGridType())); + .setSelected(GridFactory.isHexHorizontal(AppPreferences.defaultGridType.get())); getHexHorizontalIcon().setIcon(RessourceManager.getSmallIcon(Icons.GRID_HEX_HORIZONTAL)); } private void initHexVertRadio() { getHexVerticalRadio() - .setSelected(GridFactory.isHexVertical(AppPreferences.getDefaultGridType())); + .setSelected(GridFactory.isHexVertical(AppPreferences.defaultGridType.get())); getHexVerticalIcon().setIcon(RessourceManager.getSmallIcon(Icons.GRID_HEX_VERTICAL)); } private void initSquareRadio() { - getSquareRadio().setSelected(GridFactory.isSquare(AppPreferences.getDefaultGridType())); + getSquareRadio().setSelected(GridFactory.isSquare(AppPreferences.defaultGridType.get())); getSquareIcon().setIcon(RessourceManager.getSmallIcon(Icons.GRID_SQUARE)); } private void initNoGridRadio() { - getNoGridRadio().setSelected(GridFactory.isNone(AppPreferences.getDefaultGridType())); + getNoGridRadio().setSelected(GridFactory.isNone(AppPreferences.defaultGridType.get())); getNoGridIcon().setIcon(RessourceManager.getSmallIcon(Icons.GRID_NONE)); } @@ -508,7 +508,7 @@ public JTextField getPixelsPerCellTextField() { } private void initPixelsPerCellTextField() { - getPixelsPerCellTextField().setText(Integer.toString(AppPreferences.getDefaultGridSize())); + getPixelsPerCellTextField().setText(Integer.toString(AppPreferences.defaultGridSize.get())); } public JTextField getDefaultVisionTextField() { @@ -517,7 +517,7 @@ public JTextField getDefaultVisionTextField() { private void initDefaultVisionTextField() { this.getDefaultVisionTextField() - .setText(Integer.toString(AppPreferences.getDefaultVisionDistance())); + .setText(Integer.toString(AppPreferences.defaultVisionDistance.get())); } private void initVisionTypeCombo() { @@ -525,7 +525,7 @@ private void initVisionTypeCombo() { for (Zone.VisionType vt : Zone.VisionType.values()) { model.addElement(vt); } - model.setSelectedItem(AppPreferences.getDefaultVisionType()); + model.setSelectedItem(AppPreferences.defaultVisionType.get()); getVisionTypeCombo().setModel(model); } diff --git a/src/main/java/net/rptools/maptool/client/ui/preferencesdialog/PreferencesDialog.java b/src/main/java/net/rptools/maptool/client/ui/preferencesdialog/PreferencesDialog.java index 456649edfc..c59b6f40d7 100644 --- a/src/main/java/net/rptools/maptool/client/ui/preferencesdialog/PreferencesDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/preferencesdialog/PreferencesDialog.java @@ -703,30 +703,30 @@ public PreferencesDialog() { startupInfoLabel = panel.getLabel("startupInfoLabel"); pcTokenLabelFG = (ColorWell) panel.getComponent("pcTokenLabelFG"); - pcTokenLabelFG.setColor(AppPreferences.getPCMapLabelFG()); + pcTokenLabelFG.setColor(AppPreferences.pcMapLabelForeground.get()); pcTokenLabelBG = (ColorWell) panel.getComponent("pcTokenLabelBG"); - pcTokenLabelBG.setColor(AppPreferences.getPCMapLabelBG()); + pcTokenLabelBG.setColor(AppPreferences.pcMapLabelBackground.get()); pcTokenLabelBorderColor = (ColorWell) panel.getComponent("pcTokenLabelBorder"); - pcTokenLabelBorderColor.setColor(AppPreferences.getPCMapLabelBorder()); + pcTokenLabelBorderColor.setColor(AppPreferences.pcMapLabelBorder.get()); npcTokenLabelFG = (ColorWell) panel.getComponent("npcTokenLabelFG"); - npcTokenLabelFG.setColor(AppPreferences.getNPCMapLabelFG()); + npcTokenLabelFG.setColor(AppPreferences.npcMapLabelForeground.get()); npcTokenLabelBG = (ColorWell) panel.getComponent("npcTokenLabelBG"); - npcTokenLabelBG.setColor(AppPreferences.getNPCMapLabelBG()); + npcTokenLabelBG.setColor(AppPreferences.npcMapLabelBackground.get()); npcTokenLabelBorderColor = (ColorWell) panel.getComponent("npcTokenLabelBorder"); - npcTokenLabelBorderColor.setColor(AppPreferences.getNPCMapLabelBorder()); + npcTokenLabelBorderColor.setColor(AppPreferences.npcMapLabelBorder.get()); nonVisTokenLabelFG = (ColorWell) panel.getComponent("nonVisTokenLabelFG"); - nonVisTokenLabelFG.setColor(AppPreferences.getNonVisMapLabelFG()); + nonVisTokenLabelFG.setColor(AppPreferences.nonVisibleTokenMapLabelForeground.get()); nonVisTokenLabelBG = (ColorWell) panel.getComponent("nonVisTokenLabelBG"); - nonVisTokenLabelBG.setColor(AppPreferences.getNonVisMapLabelBG()); + nonVisTokenLabelBG.setColor(AppPreferences.nonVisibleTokenMapLabelBackground.get()); nonVisTokenLabelBorderColor = (ColorWell) panel.getComponent("nonVisTokenLabelBorder"); - nonVisTokenLabelBorderColor.setColor(AppPreferences.getNonVisMapLabelBorder()); + nonVisTokenLabelBorderColor.setColor(AppPreferences.nonVisibleTokenMapLabelBorder.get()); labelFontSizeSpinner = (JSpinner) panel.getComponent("labelFontSizeSpinner"); - labelFontSizeSpinner.setValue(AppPreferences.getMapLabelFontSize()); + labelFontSizeSpinner.setValue(AppPreferences.mapLabelFontSize.get()); labelBorderWidthSpinner = (JSpinner) panel.getComponent("labelBorderWidthSpinner"); - labelBorderWidthSpinner.setValue(AppPreferences.getMapLabelBorderWidth()); + labelBorderWidthSpinner.setValue(AppPreferences.mapLabelBorderWidth.get()); labelBorderArcSpinner = (JSpinner) panel.getComponent("labelBorderArcSpinner"); - labelBorderArcSpinner.setValue(AppPreferences.getMapLabelBorderArc()); + labelBorderArcSpinner.setValue(AppPreferences.mapLabelBorderArc.get()); showLabelBorderCheckBox = (JCheckBox) panel.getComponent("showLabelBorder"); showLabelBorderCheckBox.addActionListener( e -> { @@ -736,18 +736,18 @@ public PreferencesDialog() { nonVisTokenLabelBorderColor.setVisible(true); // Disabling a color well does not work labelBorderWidthSpinner.setEnabled(true); labelBorderArcSpinner.setEnabled(true); - AppPreferences.setShowMapLabelBorder(true); + AppPreferences.mapLabelShowBorder.set(true); } else { pcTokenLabelBorderColor.setVisible(false); // Disabling a color well does not work npcTokenLabelBorderColor.setVisible(false); // Disabling a color well does not work nonVisTokenLabelBorderColor.setVisible(false); // Disabling a color well does not work labelBorderWidthSpinner.setEnabled(false); labelBorderArcSpinner.setEnabled(false); - AppPreferences.setShowMapLabelBorder(false); + AppPreferences.mapLabelShowBorder.set(false); } }); - boolean showBorder = AppPreferences.getShowMapLabelBorder(); + boolean showBorder = AppPreferences.mapLabelShowBorder.get(); showLabelBorderCheckBox.setSelected(showBorder); if (showBorder) { pcTokenLabelBorderColor.setVisible(true); @@ -821,19 +821,19 @@ public PreferencesDialog() { // And keep it updated facingFaceEdges.addActionListener( e -> { - AppPreferences.setFaceEdge(facingFaceEdges.isSelected()); + AppPreferences.faceEdge.set(facingFaceEdges.isSelected()); }); facingFaceVertices.addActionListener( e -> { - AppPreferences.setFaceVertex(facingFaceVertices.isSelected()); + AppPreferences.faceVertex.set(facingFaceVertices.isSelected()); }); toolTipInlineRolls.addActionListener( - e -> AppPreferences.setUseToolTipForInlineRoll(toolTipInlineRolls.isSelected())); + e -> AppPreferences.useToolTipForInlineRoll.set(toolTipInlineRolls.isSelected())); suppressToolTipsMacroLinks.addActionListener( e -> - AppPreferences.setSuppressToolTipsForMacroLinks( + AppPreferences.suppressToolTipsForMacroLinks.set( suppressToolTipsMacroLinks.isSelected())); toolTipInitialDelay @@ -842,7 +842,7 @@ public PreferencesDialog() { new DocumentListenerProxy(toolTipInitialDelay) { @Override protected void storeNumericValue(Integer value) { - AppPreferences.setToolTipInitialDelay(value); + AppPreferences.toolTipInitialDelay.set(value); ToolTipManager.sharedInstance().setInitialDelay(value); } @@ -857,7 +857,7 @@ protected Integer convertString(String value) throws ParseException { new DocumentListenerProxy(toolTipDismissDelay) { @Override protected void storeNumericValue(Integer value) { - AppPreferences.setToolTipDismissDelay(value); + AppPreferences.toolTipDismissDelay.set(value); ToolTipManager.sharedInstance().setDismissDelay(value); } @@ -869,39 +869,43 @@ protected Integer convertString(String value) throws ParseException { chatNotificationColor.addActionListener( e -> { - AppPreferences.setChatNotificationColor(chatNotificationColor.getColor()); - MapTool.getFrame().setChatTypingLabelColor(AppPreferences.getChatNotificationColor()); + AppPreferences.chatNotificationColor.set(chatNotificationColor.getColor()); + MapTool.getFrame().setChatTypingLabelColor(AppPreferences.chatNotificationColor.get()); }); trustedOutputForeground.addActionListener( e -> { - AppPreferences.setTrustedPrefixFG(trustedOutputForeground.getColor()); + AppPreferences.trustedPrefixForeground.set(trustedOutputForeground.getColor()); MapTool.getFrame() .getCommandPanel() .setTrustedMacroPrefixColors( - AppPreferences.getTrustedPrefixFG(), AppPreferences.getTrustedPrefixBG()); + AppPreferences.trustedPrefixForeground.get(), + AppPreferences.trustedPrefixBackground.get()); }); trustedOutputBackground.addActionListener( e -> { - AppPreferences.setTrustedPrefixBG(trustedOutputBackground.getColor()); + AppPreferences.trustedPrefixBackground.set(trustedOutputBackground.getColor()); MapTool.getFrame() .getCommandPanel() .setTrustedMacroPrefixColors( - AppPreferences.getTrustedPrefixFG(), AppPreferences.getTrustedPrefixBG()); + AppPreferences.trustedPrefixForeground.get(), + AppPreferences.trustedPrefixBackground.get()); }); chatAutosaveTime.addChangeListener( new ChangeListenerProxy() { @Override protected void storeSpinnerValue(int value) { - AppPreferences.setChatAutosaveTime(value); + if (value >= 0) { + AppPreferences.chatAutoSaveTimeInMinutes.set(value); + } } }); typingNotificationDuration.addChangeListener( new ChangeListenerProxy() { @Override protected void storeSpinnerValue(int value) { - AppPreferences.setTypingNotificationDuration(value); + AppPreferences.typingNotificationDurationInSeconds.set(value); } }); @@ -914,32 +918,32 @@ public void focusLost(FocusEvent e) { if (saveFile.indexOf(".") < 0) { saveFile.append(".html"); } - AppPreferences.setChatFilenameFormat(saveFile.toString()); + AppPreferences.chatFilenameFormat.set(saveFile.toString()); } } }); allowPlayerMacroEditsDefault.addActionListener( e -> - AppPreferences.setAllowPlayerMacroEditsDefault( + AppPreferences.allowPlayerMacroEditsDefault.set( allowPlayerMacroEditsDefault.isSelected())); showAvatarInChat.addActionListener( - e -> AppPreferences.setShowAvatarInChat(showAvatarInChat.isSelected())); + e -> AppPreferences.showAvatarInChat.set(showAvatarInChat.isSelected())); saveReminderCheckBox.addActionListener( - e -> AppPreferences.setSaveReminder(saveReminderCheckBox.isSelected())); + e -> AppPreferences.saveReminder.set(saveReminderCheckBox.isSelected())); fillSelectionCheckBox.addActionListener( - e -> AppPreferences.setFillSelectionBox(fillSelectionCheckBox.isSelected())); + e -> AppPreferences.fillSelectionBox.set(fillSelectionCheckBox.isSelected())); frameRateCapTextField .getDocument() .addDocumentListener( new DocumentListenerProxy(frameRateCapTextField) { @Override protected void storeNumericValue(Integer value) { - AppPreferences.setFrameRateCap(value); + AppPreferences.frameRateCap.set(value); // AppPreferences may have rejected the value, so read it back. - final var cap = AppPreferences.getFrameRateCap(); + final var cap = AppPreferences.frameRateCap.get(); for (final var renderer : MapTool.getFrame().getZoneRenderers()) { renderer.setFrameRateCap(cap); } @@ -956,10 +960,10 @@ protected Integer convertString(String value) throws ParseException { }); renderPerformanceComboBox.setModel( - getLocalizedModel(renderPerformanceComboItems, AppPreferences.getRenderQuality().name())); + getLocalizedModel(renderPerformanceComboItems, AppPreferences.renderQuality.get().name())); renderPerformanceComboBox.addItemListener( e -> { - AppPreferences.setRenderQuality( + AppPreferences.renderQuality.set( RenderQuality.valueOf( ((LocalizedComboItem) renderPerformanceComboBox.getSelectedItem()).getValue())); }); @@ -970,69 +974,71 @@ protected Integer convertString(String value) throws ParseException { public void focusLost(FocusEvent e) { if (!e.isTemporary()) { StringBuilder userName = new StringBuilder(defaultUsername.getText()); - AppPreferences.setDefaultUserName(userName.toString()); + AppPreferences.defaultUserName.set(userName.toString()); } } }); loadMRUcheckbox.addActionListener( - e -> AppPreferences.setLoadMRUCampaignAtStart(loadMRUcheckbox.isSelected())); + e -> AppPreferences.loadMruCampaignAtStart.set(loadMRUcheckbox.isSelected())); allowExternalMacroAccessCheckBox.addActionListener( e -> - AppPreferences.setAllowExternalMacroAccess( + AppPreferences.allowExternalMacroAccess.set( allowExternalMacroAccessCheckBox.isSelected())); showDialogOnNewToken.addActionListener( - e -> AppPreferences.setShowDialogOnNewToken(showDialogOnNewToken.isSelected())); + e -> AppPreferences.showDialogOnNewToken.set(showDialogOnNewToken.isSelected())); autoSaveSpinner.addChangeListener( ce -> { int newInterval = (Integer) autoSaveSpinner.getValue(); - AppPreferences.setAutoSaveIncrement(newInterval); + AppPreferences.autoSaveIncrement.set(newInterval); }); newMapsHaveFOWCheckBox.addActionListener( - e -> AppPreferences.setNewMapsHaveFOW(newMapsHaveFOWCheckBox.isSelected())); + e -> AppPreferences.newMapsHaveFow.set(newMapsHaveFOWCheckBox.isSelected())); tokensPopupWarningWhenDeletedCheckBox.addActionListener( e -> - AppPreferences.setTokensWarnWhenDeleted( + AppPreferences.tokensWarnWhenDeleted.set( tokensPopupWarningWhenDeletedCheckBox.isSelected())); tokensStartSnapToGridCheckBox.addActionListener( - e -> AppPreferences.setTokensStartSnapToGrid(tokensStartSnapToGridCheckBox.isSelected())); + e -> AppPreferences.tokensStartSnapToGrid.set(tokensStartSnapToGridCheckBox.isSelected())); tokensSnapWhileDraggingCheckBox.addActionListener( e -> - AppPreferences.setTokensSnapWhileDragging( + AppPreferences.tokensSnapWhileDragging.set( tokensSnapWhileDraggingCheckBox.isSelected())); hideMousePointerWhileDraggingCheckBox.addActionListener( e -> - AppPreferences.setHideMousePointerWhileDragging( + AppPreferences.hideMousePointerWhileDragging.set( hideMousePointerWhileDraggingCheckBox.isSelected())); hideTokenStackIndicatorCheckBox.addActionListener( e -> - AppPreferences.setHideTokenStackIndicator( + AppPreferences.hideTokenStackIndicator.set( hideTokenStackIndicatorCheckBox.isSelected())); newMapsVisibleCheckBox.addActionListener( - e -> AppPreferences.setNewMapsVisible(newMapsVisibleCheckBox.isSelected())); + e -> AppPreferences.newMapsVisible.set(newMapsVisibleCheckBox.isSelected())); newTokensVisibleCheckBox.addActionListener( - e -> AppPreferences.setNewTokensVisible(newTokensVisibleCheckBox.isSelected())); + e -> AppPreferences.newTokensVisible.set(newTokensVisibleCheckBox.isSelected())); stampsStartFreeSizeCheckBox.addActionListener( - e -> AppPreferences.setObjectsStartFreesize(stampsStartFreeSizeCheckBox.isSelected())); + e -> AppPreferences.objectsStartFreesize.set(stampsStartFreeSizeCheckBox.isSelected())); tokensStartFreeSizeCheckBox.addActionListener( - e -> AppPreferences.setTokensStartFreesize(tokensStartFreeSizeCheckBox.isSelected())); + e -> AppPreferences.tokensStartFreesize.set(tokensStartFreeSizeCheckBox.isSelected())); stampsStartSnapToGridCheckBox.addActionListener( - e -> AppPreferences.setObjectsStartSnapToGrid(stampsStartSnapToGridCheckBox.isSelected())); + e -> AppPreferences.objectsStartSnapToGrid.set(stampsStartSnapToGridCheckBox.isSelected())); showStatSheetCheckBox.addActionListener( - e -> AppPreferences.setShowStatSheet(showStatSheetCheckBox.isSelected())); + e -> AppPreferences.showStatSheet.set(showStatSheetCheckBox.isSelected())); showPortraitCheckBox.addActionListener( - e -> AppPreferences.setShowPortrait(showPortraitCheckBox.isSelected())); + e -> AppPreferences.showPortrait.set(showPortraitCheckBox.isSelected())); showStatSheetModifierCheckBox.addActionListener( - e -> AppPreferences.setShowStatSheetModifier(showStatSheetModifierCheckBox.isSelected())); + e -> + AppPreferences.showStatSheetRequiresModifierKey.set( + showStatSheetModifierCheckBox.isSelected())); forceFacingArrowCheckBox.addActionListener( - e -> AppPreferences.setForceFacingArrow(forceFacingArrowCheckBox.isSelected())); + e -> AppPreferences.forceFacingArrow.set(forceFacingArrowCheckBox.isSelected())); backgroundsStartFreeSizeCheckBox.addActionListener( e -> - AppPreferences.setBackgroundsStartFreesize( + AppPreferences.backgroundsStartFreesize.set( backgroundsStartFreeSizeCheckBox.isSelected())); backgroundsStartSnapToGridCheckBox.addActionListener( e -> - AppPreferences.setBackgroundsStartSnapToGrid( + AppPreferences.backgroundsStartSnapToGrid.set( backgroundsStartSnapToGridCheckBox.isSelected())); defaultGridSizeTextField .getDocument() @@ -1040,7 +1046,7 @@ public void focusLost(FocusEvent e) { new DocumentListenerProxy(defaultGridSizeTextField) { @Override protected void storeNumericValue(Integer value) { - AppPreferences.setDefaultGridSize(value); + AppPreferences.defaultGridSize.set(value); } @Override @@ -1055,7 +1061,7 @@ protected Integer convertString(String value) throws ParseException { new DocumentListenerProxy(defaultUnitsPerCellTextField) { @Override protected void storeNumericValue(Double value) { - AppPreferences.setDefaultUnitsPerCell(value); + AppPreferences.defaultUnitsPerCell.set(value); } @Override @@ -1069,7 +1075,7 @@ protected Double convertString(String value) throws ParseException { new DocumentListenerProxy(defaultVisionDistanceTextField) { @Override protected void storeNumericValue(Integer value) { - AppPreferences.setDefaultVisionDistance(value); + AppPreferences.defaultVisionDistance.set(value); } @Override @@ -1083,7 +1089,7 @@ protected Integer convertString(String value) throws ParseException { new DocumentListenerProxy(statsheetPortraitSize) { @Override protected void storeNumericValue(Integer value) { - AppPreferences.setPortraitSize(value); + AppPreferences.portraitSize.set(value); } @Override @@ -1092,7 +1098,7 @@ protected Integer convertString(String value) throws ParseException { } }); haloLineWidthSpinner.addChangeListener( - ce -> AppPreferences.setHaloLineWidth((Integer) haloLineWidthSpinner.getValue())); + ce -> AppPreferences.haloLineWidth.set((Integer) haloLineWidthSpinner.getValue())); // Overlay opacity options in AppPreferences, with // error checking to ensure values are within the acceptable range @@ -1101,7 +1107,7 @@ protected Integer convertString(String value) throws ParseException { new ChangeListenerProxy() { @Override protected void storeSpinnerValue(int value) { - AppPreferences.setHaloOverlayOpacity(value); + AppPreferences.haloOverlayOpacity.set(value); MapTool.getFrame().refresh(); } }); @@ -1109,7 +1115,7 @@ protected void storeSpinnerValue(int value) { new ChangeListenerProxy() { @Override protected void storeSpinnerValue(int value) { - AppPreferences.setAuraOverlayOpacity(value); + AppPreferences.auraOverlayOpacity.set(value); MapTool.getFrame().refresh(); } }); @@ -1117,7 +1123,7 @@ protected void storeSpinnerValue(int value) { new ChangeListenerProxy() { @Override protected void storeSpinnerValue(int value) { - AppPreferences.setLightOverlayOpacity(value); + AppPreferences.lightOverlayOpacity.set(value); MapTool.getFrame().refresh(); } }); @@ -1125,7 +1131,7 @@ protected void storeSpinnerValue(int value) { new ChangeListenerProxy() { @Override protected void storeSpinnerValue(int value) { - AppPreferences.setLumensOverlayOpacity(value); + AppPreferences.lumensOverlayOpacity.set(value); MapTool.getFrame().refresh(); } }); @@ -1133,42 +1139,46 @@ protected void storeSpinnerValue(int value) { new ChangeListenerProxy() { @Override protected void storeSpinnerValue(int value) { - AppPreferences.setLumensOverlayBorderThickness(value); + AppPreferences.lumensOverlayBorderThickness.set(value); MapTool.getFrame().refresh(); } }); lumensOverlayShowByDefaultCheckBox.addActionListener( e -> - AppPreferences.setLumensOverlayShowByDefault( + AppPreferences.lumensOverlayShowByDefault.set( lumensOverlayShowByDefaultCheckBox.isSelected())); lightsShowByDefaultCheckBox.addActionListener( - e -> AppPreferences.setLightsShowByDefault(lightsShowByDefaultCheckBox.isSelected())); + e -> AppPreferences.lightsShowByDefault.set(lightsShowByDefaultCheckBox.isSelected())); fogOverlayOpacitySpinner.addChangeListener( new ChangeListenerProxy() { @Override protected void storeSpinnerValue(int value) { - AppPreferences.setFogOverlayOpacity(value); + AppPreferences.fogOverlayOpacity.set(value); + + // FIXME Force ModelChange event to flush fog from zone :( + Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); + zone.setHasFog(zone.hasFog()); MapTool.getFrame().refresh(); } }); useHaloColorAsVisionOverlayCheckBox.addActionListener( e -> - AppPreferences.setUseHaloColorOnVisionOverlay( + AppPreferences.useHaloColorOnVisionOverlay.set( useHaloColorAsVisionOverlayCheckBox.isSelected())); autoRevealVisionOnGMMoveCheckBox.addActionListener( e -> - AppPreferences.setAutoRevealVisionOnGMMovement( + AppPreferences.autoRevealVisionOnGMMovement.set( autoRevealVisionOnGMMoveCheckBox.isSelected())); showSmiliesCheckBox.addActionListener( - e -> AppPreferences.setShowSmilies(showSmiliesCheckBox.isSelected())); + e -> AppPreferences.showSmilies.set(showSmiliesCheckBox.isSelected())); playSystemSoundCheckBox.addActionListener( - e -> AppPreferences.setPlaySystemSounds(playSystemSoundCheckBox.isSelected())); + e -> AppPreferences.playSystemSounds.set(playSystemSoundCheckBox.isSelected())); mapVisibilityWarning.addActionListener( - e -> AppPreferences.setMapVisibilityWarning(mapVisibilityWarning.isSelected())); + e -> AppPreferences.mapVisibilityWarning.set(mapVisibilityWarning.isSelected())); playStreamsCheckBox.addActionListener( e -> { - AppPreferences.setPlayStreams(playStreamsCheckBox.isSelected()); + AppPreferences.playStreams.set(playStreamsCheckBox.isSelected()); if (!playStreamsCheckBox.isSelected()) { MediaPlayerAdapter.stopStream("*", true, 0); } @@ -1176,11 +1186,11 @@ protected void storeSpinnerValue(int value) { playSystemSoundOnlyWhenNotFocusedCheckBox.addActionListener( e -> - AppPreferences.setPlaySystemSoundsOnlyWhenNotFocused( + AppPreferences.playSystemSoundsOnlyWhenNotFocused.set( playSystemSoundOnlyWhenNotFocusedCheckBox.isSelected())); syrinscapeActiveCheckBox.addActionListener( - e -> AppPreferences.setSyrinscapeActive(syrinscapeActiveCheckBox.isSelected())); + e -> AppPreferences.syrinscapeActive.set(syrinscapeActiveCheckBox.isSelected())); fontSizeTextField .getDocument() @@ -1188,7 +1198,7 @@ protected void storeSpinnerValue(int value) { new DocumentListenerProxy(fontSizeTextField) { @Override protected void storeNumericValue(Integer value) { - AppPreferences.setFontSize(value); + AppPreferences.fontSize.set(value); } @Override @@ -1199,62 +1209,62 @@ protected Integer convertString(String value) throws ParseException { npcTokenLabelBG.addActionListener( e -> { - AppPreferences.setNPCMapLabelBG(npcTokenLabelBG.getColor()); + AppPreferences.npcMapLabelBackground.set(npcTokenLabelBG.getColor()); }); npcTokenLabelFG.addActionListener( e -> { - AppPreferences.setNPCMapLabelFG(npcTokenLabelFG.getColor()); + AppPreferences.npcMapLabelForeground.set(npcTokenLabelFG.getColor()); }); pcTokenLabelBG.addActionListener( e -> { - AppPreferences.setPCMapLabelBG(pcTokenLabelBG.getColor()); + AppPreferences.pcMapLabelBackground.set(pcTokenLabelBG.getColor()); }); pcTokenLabelFG.addActionListener( e -> { - AppPreferences.setPCMapLabelFG(pcTokenLabelFG.getColor()); + AppPreferences.pcMapLabelForeground.set(pcTokenLabelFG.getColor()); }); nonVisTokenLabelBG.addActionListener( e -> { - AppPreferences.setNonVisMapLabelBG(nonVisTokenLabelBG.getColor()); + AppPreferences.nonVisibleTokenMapLabelBackground.set(nonVisTokenLabelBG.getColor()); }); nonVisTokenLabelFG.addActionListener( e -> { - AppPreferences.setNonVisMapLabelFG(nonVisTokenLabelFG.getColor()); + AppPreferences.nonVisibleTokenMapLabelForeground.set(nonVisTokenLabelFG.getColor()); }); labelFontSizeSpinner.addChangeListener( new ChangeListenerProxy() { @Override protected void storeSpinnerValue(int value) { - AppPreferences.setMapLabelFontSize(Math.max(value, 0)); + AppPreferences.mapLabelFontSize.set(Math.max(value, 0)); } }); pcTokenLabelBorderColor.addActionListener( e -> { - AppPreferences.setPCMapLabelBorder(pcTokenLabelBorderColor.getColor()); + AppPreferences.pcMapLabelBorder.set(pcTokenLabelBorderColor.getColor()); }); npcTokenLabelBorderColor.addActionListener( e -> { - AppPreferences.setNPCMapLabelBorder(npcTokenLabelBorderColor.getColor()); + AppPreferences.npcMapLabelBorder.set(npcTokenLabelBorderColor.getColor()); }); nonVisTokenLabelBorderColor.addActionListener( e -> { - AppPreferences.setNonVisMapLabelBorder(nonVisTokenLabelBorderColor.getColor()); + AppPreferences.nonVisibleTokenMapLabelBorder.set(nonVisTokenLabelBorderColor.getColor()); }); labelBorderWidthSpinner.addChangeListener( new ChangeListenerProxy() { @Override protected void storeSpinnerValue(int value) { - AppPreferences.setMapLabelBorderWidth(Math.max(value, 0)); + AppPreferences.mapLabelBorderWidth.set(Math.max(value, 0)); } }); @@ -1262,25 +1272,28 @@ protected void storeSpinnerValue(int value) { new ChangeListenerProxy() { @Override protected void storeSpinnerValue(int value) { - AppPreferences.setMapLabelBorderArc(Math.max(value, 0)); + AppPreferences.mapLabelBorderArc.set(Math.max(value, 0)); } }); - fitGMView.addActionListener(e -> AppPreferences.setFitGMView(fitGMView.isSelected())); - hideNPCs.addActionListener(e -> AppPreferences.setInitHideNpcs(hideNPCs.isSelected())); + fitGMView.addActionListener(e -> AppPreferences.fitGmView.set(fitGMView.isSelected())); + hideNPCs.addActionListener( + e -> AppPreferences.initiativePanelHidesNpcs.set(hideNPCs.isSelected())); ownerPermissions.addActionListener( - e -> AppPreferences.setInitOwnerPermissions(ownerPermissions.isSelected())); + e -> + AppPreferences.initiativePanelAllowsOwnerPermissions.set( + ownerPermissions.isSelected())); lockMovement.addActionListener( - e -> AppPreferences.setInitLockMovement(lockMovement.isSelected())); + e -> AppPreferences.initiativeMovementLocked.set(lockMovement.isSelected())); showInitGainMessage.addActionListener( - e -> AppPreferences.setShowInitGainMessage(showInitGainMessage.isSelected())); + e -> AppPreferences.showInitiativeGainedMessage.set(showInitGainMessage.isSelected())); upnpDiscoveryTimeoutTextField .getDocument() .addDocumentListener( new DocumentListenerProxy(upnpDiscoveryTimeoutTextField) { @Override protected void storeNumericValue(Integer value) { - AppPreferences.setUpnpDiscoveryTimeout(value); + AppPreferences.upnpDiscoveryTimeout.set(value); } @Override @@ -1290,7 +1303,7 @@ protected Integer convertString(String value) throws ParseException { }); fileSyncPathButton.addActionListener( e -> { - JFileChooser fileChooser = new JFileChooser(AppPreferences.getFileSyncPath()); + JFileChooser fileChooser = new JFileChooser(AppPreferences.fileSyncPath.get()); fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int returnVal = fileChooser.showOpenDialog(null); @@ -1302,7 +1315,7 @@ protected Integer convertString(String value) throws ParseException { fileSyncPath.setCaretPosition(0); // Save to preferences - AppPreferences.setFileSyncPath(selectedPath); + AppPreferences.fileSyncPath.set(selectedPath); } }); jvmXmxTextField.addFocusListener( @@ -1395,55 +1408,56 @@ public void focusLost(FocusEvent e) { chatNotificationShowBackground.addActionListener( e -> - AppPreferences.setChatNotificationShowBackground( + AppPreferences.chatNotificationBackground.set( chatNotificationShowBackground.isSelected())); defaultGridTypeCombo.setModel( - getLocalizedModel(defaultGridTypeComboItems, AppPreferences.getDefaultGridType())); + getLocalizedModel(defaultGridTypeComboItems, AppPreferences.defaultGridType.get())); defaultGridTypeCombo.addItemListener( e -> - AppPreferences.setDefaultGridType( + AppPreferences.defaultGridType.set( ((LocalizedComboItem) (defaultGridTypeCombo.getSelectedItem())).getValue())); duplicateTokenCombo.setModel( - getLocalizedModel(duplicateTokenComboItems, AppPreferences.getDuplicateTokenNumber())); + getLocalizedModel(duplicateTokenComboItems, AppPreferences.duplicateTokenNumber.get())); duplicateTokenCombo.addItemListener( e -> - AppPreferences.setDuplicateTokenNumber( + AppPreferences.duplicateTokenNumber.set( ((LocalizedComboItem) (duplicateTokenCombo.getSelectedItem())).getValue())); showNumberingCombo.setModel( - getLocalizedModel(showNumberingComboItems, AppPreferences.getTokenNumberDisplay())); + getLocalizedModel(showNumberingComboItems, AppPreferences.tokenNumberDisplay.get())); showNumberingCombo.addItemListener( e -> - AppPreferences.setTokenNumberDisplay( + AppPreferences.tokenNumberDisplay.set( ((LocalizedComboItem) showNumberingCombo.getSelectedItem()).getValue())); tokenNamingCombo.setModel( - getLocalizedModel(tokenNamingComboItems, AppPreferences.getNewTokenNaming())); + getLocalizedModel(tokenNamingComboItems, AppPreferences.newTokenNaming.get())); tokenNamingCombo.addItemListener( e -> - AppPreferences.setNewTokenNaming( + AppPreferences.newTokenNaming.set( ((LocalizedComboItem) (tokenNamingCombo.getSelectedItem())).getValue())); movementMetricCombo.setModel(new DefaultComboBoxModel<>(movementMetricComboItems)); - movementMetricCombo.setSelectedItem(AppPreferences.getMovementMetric()); + movementMetricCombo.setSelectedItem(AppPreferences.movementMetric.get()); movementMetricCombo.addItemListener( e -> - AppPreferences.setMovementMetric((WalkerMetric) movementMetricCombo.getSelectedItem())); + AppPreferences.movementMetric.set( + (WalkerMetric) movementMetricCombo.getSelectedItem())); visionTypeCombo.setModel(new DefaultComboBoxModel<>(Zone.VisionType.values())); - visionTypeCombo.setSelectedItem(AppPreferences.getDefaultVisionType()); + visionTypeCombo.setSelectedItem(AppPreferences.defaultVisionType.get()); visionTypeCombo.addItemListener( e -> - AppPreferences.setDefaultVisionType( + AppPreferences.defaultVisionType.set( (Zone.VisionType) visionTypeCombo.getSelectedItem())); mapSortType.setModel(new DefaultComboBoxModel<>(AppPreferences.MapSortType.values())); - mapSortType.setSelectedItem(AppPreferences.getMapSortType()); + mapSortType.setSelectedItem(AppPreferences.mapSortType.get()); mapSortType.addItemListener( e -> - AppPreferences.setMapSortType( + AppPreferences.mapSortType.set( (AppPreferences.MapSortType) mapSortType.getSelectedItem())); macroEditorThemeCombo.setModel(new DefaultComboBoxModel<>()); @@ -1455,22 +1469,22 @@ public void focusLost(FocusEvent e) { p -> macroEditorThemeCombo.addItem( FilenameUtils.removeExtension(p.getFileName().toString()))); - macroEditorThemeCombo.setSelectedItem(AppPreferences.getDefaultMacroEditorTheme()); + macroEditorThemeCombo.setSelectedItem(AppPreferences.defaultMacroEditorTheme.get()); } catch (IOException ioe) { log.warn("Unable to list macro editor themes.", ioe); macroEditorThemeCombo.addItem("Default"); } macroEditorThemeCombo.addItemListener( e -> - AppPreferences.setDefaultMacroEditorTheme( + AppPreferences.defaultMacroEditorTheme.set( (String) macroEditorThemeCombo.getSelectedItem())); iconThemeCombo.setModel(new DefaultComboBoxModel<>()); iconThemeCombo.addItem(RessourceManager.CLASSIC); iconThemeCombo.addItem(RessourceManager.ROD_TAKEHARA); - iconThemeCombo.setSelectedItem(AppPreferences.getIconTheme()); + iconThemeCombo.setSelectedItem(AppPreferences.iconTheme.get()); iconThemeCombo.addItemListener( - e -> AppPreferences.setIconTheme((String) iconThemeCombo.getSelectedItem())); + e -> AppPreferences.iconTheme.set((String) iconThemeCombo.getSelectedItem())); themeFilterCombo.setModel(getLocalizedModel(themeFilterComboItems, "All")); themeFilterCombo.addItemListener( @@ -1527,92 +1541,94 @@ public void setVisible(boolean b) { * method is called during the initialization process. */ private void setInitialState() { - showDialogOnNewToken.setSelected(AppPreferences.getShowDialogOnNewToken()); - saveReminderCheckBox.setSelected(AppPreferences.getSaveReminder()); - fillSelectionCheckBox.setSelected(AppPreferences.getFillSelectionBox()); - frameRateCapTextField.setText(Integer.toString(AppPreferences.getFrameRateCap())); - defaultUsername.setText(AppPreferences.getDefaultUserName()); + showDialogOnNewToken.setSelected(AppPreferences.showDialogOnNewToken.get()); + saveReminderCheckBox.setSelected(AppPreferences.saveReminder.get()); + fillSelectionCheckBox.setSelected(AppPreferences.fillSelectionBox.get()); + frameRateCapTextField.setText(Integer.toString(AppPreferences.frameRateCap.get())); + defaultUsername.setText(AppPreferences.defaultUserName.get()); // initEnableServerSyncCheckBox.setSelected(AppPreferences.getInitEnableServerSync()); - autoSaveSpinner.setValue(AppPreferences.getAutoSaveIncrement()); - loadMRUcheckbox.setSelected(AppPreferences.getLoadMRUCampaignAtStart()); - newMapsHaveFOWCheckBox.setSelected(AppPreferences.getNewMapsHaveFOW()); - tokensPopupWarningWhenDeletedCheckBox.setSelected(AppPreferences.getTokensWarnWhenDeleted()); - tokensStartSnapToGridCheckBox.setSelected(AppPreferences.getTokensStartSnapToGrid()); - tokensSnapWhileDraggingCheckBox.setSelected(AppPreferences.getTokensSnapWhileDragging()); + autoSaveSpinner.setValue(AppPreferences.autoSaveIncrement.get()); + loadMRUcheckbox.setSelected(AppPreferences.loadMruCampaignAtStart.get()); + newMapsHaveFOWCheckBox.setSelected(AppPreferences.newMapsHaveFow.get()); + tokensPopupWarningWhenDeletedCheckBox.setSelected(AppPreferences.tokensWarnWhenDeleted.get()); + tokensStartSnapToGridCheckBox.setSelected(AppPreferences.tokensStartSnapToGrid.get()); + tokensSnapWhileDraggingCheckBox.setSelected(AppPreferences.tokensSnapWhileDragging.get()); hideMousePointerWhileDraggingCheckBox.setSelected( - AppPreferences.getHideMousePointerWhileDragging()); - hideTokenStackIndicatorCheckBox.setSelected(AppPreferences.getHideTokenStackIndicator()); - newMapsVisibleCheckBox.setSelected(AppPreferences.getNewMapsVisible()); - newTokensVisibleCheckBox.setSelected(AppPreferences.getNewTokensVisible()); - stampsStartFreeSizeCheckBox.setSelected(AppPreferences.getObjectsStartFreesize()); - tokensStartFreeSizeCheckBox.setSelected(AppPreferences.getTokensStartFreesize()); - stampsStartSnapToGridCheckBox.setSelected(AppPreferences.getObjectsStartSnapToGrid()); - backgroundsStartFreeSizeCheckBox.setSelected(AppPreferences.getBackgroundsStartFreesize()); - showStatSheetCheckBox.setSelected(AppPreferences.getShowStatSheet()); - showPortraitCheckBox.setSelected(AppPreferences.getShowPortrait()); - showStatSheetModifierCheckBox.setSelected(AppPreferences.getShowStatSheetModifier()); - forceFacingArrowCheckBox.setSelected(AppPreferences.getForceFacingArrow()); - backgroundsStartSnapToGridCheckBox.setSelected(AppPreferences.getBackgroundsStartSnapToGrid()); - defaultGridSizeTextField.setText(Integer.toString(AppPreferences.getDefaultGridSize())); + AppPreferences.hideMousePointerWhileDragging.get()); + hideTokenStackIndicatorCheckBox.setSelected(AppPreferences.hideTokenStackIndicator.get()); + newMapsVisibleCheckBox.setSelected(AppPreferences.newMapsVisible.get()); + newTokensVisibleCheckBox.setSelected(AppPreferences.newTokensVisible.get()); + stampsStartFreeSizeCheckBox.setSelected(AppPreferences.objectsStartFreesize.get()); + tokensStartFreeSizeCheckBox.setSelected(AppPreferences.tokensStartFreesize.get()); + stampsStartSnapToGridCheckBox.setSelected(AppPreferences.objectsStartSnapToGrid.get()); + backgroundsStartFreeSizeCheckBox.setSelected(AppPreferences.backgroundsStartFreesize.get()); + showStatSheetCheckBox.setSelected(AppPreferences.showStatSheet.get()); + showPortraitCheckBox.setSelected(AppPreferences.showPortrait.get()); + showStatSheetModifierCheckBox.setSelected( + AppPreferences.showStatSheetRequiresModifierKey.get()); + forceFacingArrowCheckBox.setSelected(AppPreferences.forceFacingArrow.get()); + backgroundsStartSnapToGridCheckBox.setSelected(AppPreferences.backgroundsStartSnapToGrid.get()); + defaultGridSizeTextField.setText(Integer.toString(AppPreferences.defaultGridSize.get())); // Localizes units per cell, using the proper separator. Fixes #507. defaultUnitsPerCellTextField.setText( - StringUtil.formatDecimal(AppPreferences.getDefaultUnitsPerCell(), 1)); + StringUtil.formatDecimal(AppPreferences.defaultUnitsPerCell.get(), 1)); defaultVisionDistanceTextField.setText( - Integer.toString(AppPreferences.getDefaultVisionDistance())); - statsheetPortraitSize.setText(Integer.toString(AppPreferences.getPortraitSize())); - fontSizeTextField.setText(Integer.toString(AppPreferences.getFontSize())); - haloLineWidthSpinner.setValue(AppPreferences.getHaloLineWidth()); - mapVisibilityWarning.setSelected(AppPreferences.getMapVisibilityWarning()); + Integer.toString(AppPreferences.defaultVisionDistance.get())); + statsheetPortraitSize.setText(Integer.toString(AppPreferences.portraitSize.get())); + fontSizeTextField.setText(Integer.toString(AppPreferences.fontSize.get())); + haloLineWidthSpinner.setValue(AppPreferences.haloLineWidth.get()); + mapVisibilityWarning.setSelected(AppPreferences.mapVisibilityWarning.get()); haloOverlayOpacitySpinner.setModel( - new SpinnerNumberModel(AppPreferences.getHaloOverlayOpacity(), 0, 255, 1)); + new SpinnerNumberModel(AppPreferences.haloOverlayOpacity.get().intValue(), 0, 255, 1)); auraOverlayOpacitySpinner.setModel( - new SpinnerNumberModel(AppPreferences.getAuraOverlayOpacity(), 0, 255, 1)); + new SpinnerNumberModel(AppPreferences.auraOverlayOpacity.get().intValue(), 0, 255, 1)); lightOverlayOpacitySpinner.setModel( - new SpinnerNumberModel(AppPreferences.getLightOverlayOpacity(), 0, 255, 1)); + new SpinnerNumberModel(AppPreferences.lightOverlayOpacity.get().intValue(), 0, 255, 1)); lumensOverlayOpacitySpinner.setModel( - new SpinnerNumberModel(AppPreferences.getLumensOverlayOpacity(), 0, 255, 1)); + new SpinnerNumberModel(AppPreferences.lumensOverlayOpacity.get().intValue(), 0, 255, 1)); lumensOverlayBorderThicknessSpinner.setModel( new SpinnerNumberModel( - AppPreferences.getLumensOverlayBorderThickness(), 0, Integer.MAX_VALUE, 1)); - lumensOverlayShowByDefaultCheckBox.setSelected(AppPreferences.getLumensOverlayShowByDefault()); - lightsShowByDefaultCheckBox.setSelected(AppPreferences.getLightsShowByDefault()); + AppPreferences.lumensOverlayBorderThickness.get().intValue(), 0, Integer.MAX_VALUE, 1)); + lumensOverlayShowByDefaultCheckBox.setSelected(AppPreferences.lumensOverlayShowByDefault.get()); + lightsShowByDefaultCheckBox.setSelected(AppPreferences.lightsShowByDefault.get()); fogOverlayOpacitySpinner.setModel( - new SpinnerNumberModel(AppPreferences.getFogOverlayOpacity(), 0, 255, 1)); + new SpinnerNumberModel(AppPreferences.fogOverlayOpacity.get().intValue(), 0, 255, 1)); useHaloColorAsVisionOverlayCheckBox.setSelected( - AppPreferences.getUseHaloColorOnVisionOverlay()); - autoRevealVisionOnGMMoveCheckBox.setSelected(AppPreferences.getAutoRevealVisionOnGMMovement()); - showSmiliesCheckBox.setSelected(AppPreferences.getShowSmilies()); - playSystemSoundCheckBox.setSelected(AppPreferences.getPlaySystemSounds()); - playStreamsCheckBox.setSelected(AppPreferences.getPlayStreams()); + AppPreferences.useHaloColorOnVisionOverlay.get()); + autoRevealVisionOnGMMoveCheckBox.setSelected(AppPreferences.autoRevealVisionOnGMMovement.get()); + showSmiliesCheckBox.setSelected(AppPreferences.showSmilies.get()); + playSystemSoundCheckBox.setSelected(AppPreferences.playSystemSounds.get()); + playStreamsCheckBox.setSelected(AppPreferences.playStreams.get()); playSystemSoundOnlyWhenNotFocusedCheckBox.setSelected( - AppPreferences.getPlaySystemSoundsOnlyWhenNotFocused()); - syrinscapeActiveCheckBox.setSelected(AppPreferences.getSyrinscapeActive()); - showAvatarInChat.setSelected(AppPreferences.getShowAvatarInChat()); - allowPlayerMacroEditsDefault.setSelected(AppPreferences.getAllowPlayerMacroEditsDefault()); - toolTipInlineRolls.setSelected(AppPreferences.getUseToolTipForInlineRoll()); - suppressToolTipsMacroLinks.setSelected(AppPreferences.getSuppressToolTipsForMacroLinks()); - trustedOutputForeground.setColor(AppPreferences.getTrustedPrefixFG()); - trustedOutputBackground.setColor(AppPreferences.getTrustedPrefixBG()); - toolTipInitialDelay.setText(Integer.toString(AppPreferences.getToolTipInitialDelay())); - toolTipDismissDelay.setText(Integer.toString(AppPreferences.getToolTipDismissDelay())); - facingFaceEdges.setSelected(AppPreferences.getFaceEdge()); - facingFaceVertices.setSelected(AppPreferences.getFaceVertex()); + AppPreferences.playSystemSoundsOnlyWhenNotFocused.get()); + syrinscapeActiveCheckBox.setSelected(AppPreferences.syrinscapeActive.get()); + showAvatarInChat.setSelected(AppPreferences.showAvatarInChat.get()); + allowPlayerMacroEditsDefault.setSelected(AppPreferences.allowPlayerMacroEditsDefault.get()); + toolTipInlineRolls.setSelected(AppPreferences.useToolTipForInlineRoll.get()); + suppressToolTipsMacroLinks.setSelected(AppPreferences.suppressToolTipsForMacroLinks.get()); + trustedOutputForeground.setColor(AppPreferences.trustedPrefixForeground.get()); + trustedOutputBackground.setColor(AppPreferences.trustedPrefixBackground.get()); + toolTipInitialDelay.setText(Integer.toString(AppPreferences.toolTipInitialDelay.get())); + toolTipDismissDelay.setText(Integer.toString(AppPreferences.toolTipDismissDelay.get())); + facingFaceEdges.setSelected(AppPreferences.faceEdge.get()); + facingFaceVertices.setSelected(AppPreferences.faceVertex.get()); chatAutosaveTime.setModel( - new SpinnerNumberModel(AppPreferences.getChatAutosaveTime(), 0, 24 * 60, 1)); - chatFilenameFormat.setText(AppPreferences.getChatFilenameFormat()); - - fitGMView.setSelected(AppPreferences.getFitGMView()); - hideNPCs.setSelected(AppPreferences.getInitHideNpcs()); - ownerPermissions.setSelected(AppPreferences.getInitOwnerPermissions()); - lockMovement.setSelected(AppPreferences.getInitLockMovement()); - showInitGainMessage.setSelected(AppPreferences.isShowInitGainMessage()); + new SpinnerNumberModel( + AppPreferences.chatAutoSaveTimeInMinutes.get().intValue(), 0, 24 * 60, 1)); + chatFilenameFormat.setText(AppPreferences.chatFilenameFormat.get()); + + fitGMView.setSelected(AppPreferences.fitGmView.get()); + hideNPCs.setSelected(AppPreferences.initiativePanelHidesNpcs.get()); + ownerPermissions.setSelected(AppPreferences.initiativePanelAllowsOwnerPermissions.get()); + lockMovement.setSelected(AppPreferences.initiativeMovementLocked.get()); + showInitGainMessage.setSelected(AppPreferences.showInitiativeGainedMessage.get()); upnpDiscoveryTimeoutTextField.setText( - Integer.toString(AppPreferences.getUpnpDiscoveryTimeout())); - allowExternalMacroAccessCheckBox.setSelected(AppPreferences.getAllowExternalMacroAccess()); - fileSyncPath.setText(AppPreferences.getFileSyncPath()); + Integer.toString(AppPreferences.upnpDiscoveryTimeout.get())); + allowExternalMacroAccessCheckBox.setSelected(AppPreferences.allowExternalMacroAccess.get()); + fileSyncPath.setText(AppPreferences.fileSyncPath.get()); // get JVM User Defaults/User override preferences if (!UserJvmOptions.loadAppCfg()) { @@ -1637,7 +1653,7 @@ private void setInitialState() { } } - Integer rawVal = AppPreferences.getTypingNotificationDuration(); + Integer rawVal = AppPreferences.typingNotificationDurationInSeconds.get(); Integer typingVal = null; if (rawVal != null && rawVal > 99) { // backward compatibility -- used to be stored in ms, now in seconds @@ -1651,14 +1667,15 @@ private void setInitialState() { } } int value = Math.abs((typingVal == null || typingVal > rawVal) ? rawVal : typingVal); - AppPreferences.setTypingNotificationDuration(value); + AppPreferences.typingNotificationDurationInSeconds.set(value); SpinnerNumberModel typingDurationModel = - new SpinnerNumberModel((int) AppPreferences.getTypingNotificationDuration(), 0, 99, 1); + new SpinnerNumberModel( + (int) AppPreferences.typingNotificationDurationInSeconds.get(), 0, 99, 1); typingNotificationDuration.setModel(typingDurationModel); - chatNotificationColor.setColor(AppPreferences.getChatNotificationColor()); - chatNotificationShowBackground.setSelected(AppPreferences.getChatNotificationShowBackground()); + chatNotificationColor.setColor(AppPreferences.chatNotificationColor.get()); + chatNotificationShowBackground.setSelected(AppPreferences.chatNotificationBackground.get()); CompletableFuture keys = new PublicPrivateKeyStore().getKeys(); 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 b5bcab3396..dea057934c 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 @@ -39,8 +39,8 @@ public class StatSheetListener { */ @Subscribe public void onHoverEnter(TokenHoverEnter event) { - if (AppPreferences.getShowStatSheet() - && AppPreferences.getShowStatSheetModifier() == event.shiftDown()) { + if (AppPreferences.showStatSheet.get() + && AppPreferences.showStatSheetRequiresModifierKey.get() == event.shiftDown()) { var ssManager = new StatSheetManager(); if (statSheet == null && !ssManager.isLegacyStatSheet(event.token().getStatSheet())) { /* diff --git a/src/main/java/net/rptools/maptool/client/ui/startserverdialog/StartServerDialog.java b/src/main/java/net/rptools/maptool/client/ui/startserverdialog/StartServerDialog.java index 4a497b26cb..2fba976d20 100644 --- a/src/main/java/net/rptools/maptool/client/ui/startserverdialog/StartServerDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/startserverdialog/StartServerDialog.java @@ -143,7 +143,7 @@ public void showDialog() { movementMetricModel.addElement(WalkerMetric.ONE_ONE_ONE); movementMetricModel.addElement(WalkerMetric.MANHATTAN); movementMetricModel.addElement(WalkerMetric.NO_DIAGONALS); - movementMetricModel.setSelectedItem(AppPreferences.getMovementMetric()); + movementMetricModel.setSelectedItem(AppPreferences.movementMetric.get()); movementMetricCombo.setModel(movementMetricModel); movementMetricCombo.addItemListener( diff --git a/src/main/java/net/rptools/maptool/client/ui/startserverdialog/StartServerDialogPreferences.java b/src/main/java/net/rptools/maptool/client/ui/startserverdialog/StartServerDialogPreferences.java index 0b02a8e693..82210178a4 100644 --- a/src/main/java/net/rptools/maptool/client/ui/startserverdialog/StartServerDialogPreferences.java +++ b/src/main/java/net/rptools/maptool/client/ui/startserverdialog/StartServerDialogPreferences.java @@ -182,7 +182,7 @@ public boolean getUseToolTipsForUnformattedRolls() { // to the user to configure before the start server dialog. So if it has not been // specified we default to the users preferences. return Objects.requireNonNullElseGet( - useToolTipsForUnformattedRolls, AppPreferences::getUseToolTipForInlineRoll); + useToolTipsForUnformattedRolls, AppPreferences.useToolTipForInlineRoll::get); } public void setUseToolTipsForUnformattedRolls(boolean flag) { diff --git a/src/main/java/net/rptools/maptool/client/ui/theme/RessourceManager.java b/src/main/java/net/rptools/maptool/client/ui/theme/RessourceManager.java index 4c1353ce1f..8ff6c4ef70 100644 --- a/src/main/java/net/rptools/maptool/client/ui/theme/RessourceManager.java +++ b/src/main/java/net/rptools/maptool/client/ui/theme/RessourceManager.java @@ -501,7 +501,7 @@ public static ImageIcon getBigIcon(Icons icon) { private static ImageIcon getIcon(Icons icon, int width, int height) { var iconPaths = classicIcons; - switch (AppPreferences.getIconTheme()) { + switch (AppPreferences.iconTheme.get()) { case ROD_TAKEHARA -> iconPaths = rodIcons; } diff --git a/src/main/java/net/rptools/maptool/client/ui/token/dialog/create/NewTokenDialog.java b/src/main/java/net/rptools/maptool/client/ui/token/dialog/create/NewTokenDialog.java index 441b9bea31..c5334e756c 100644 --- a/src/main/java/net/rptools/maptool/client/ui/token/dialog/create/NewTokenDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/token/dialog/create/NewTokenDialog.java @@ -179,7 +179,7 @@ public void initOKButton() { e -> { success = true; if (!getShowDialogCheckbox().isSelected()) { - AppPreferences.setShowDialogOnNewToken(false); + AppPreferences.showDialogOnNewToken.set(false); } if (getNameTextField().getText().equals("")) { MapTool.showError(I18N.getText("msg.error.emptyTokenName")); diff --git a/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/EditTokenDialog.java b/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/EditTokenDialog.java index a5887838ea..390a3587a3 100644 --- a/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/EditTokenDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/EditTokenDialog.java @@ -1565,7 +1565,7 @@ public void initStatBlocks() { // Set the color style via Theme try { File themeFile = - new File(AppConstants.THEMES_DIR, AppPreferences.getDefaultMacroEditorTheme() + ".xml"); + new File(AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); Theme theme = Theme.load(new FileInputStream(themeFile)); theme.apply(xmlStatblockRSyntaxTextArea); @@ -1587,7 +1587,7 @@ public void initStatBlocks() { // Set the color style via Theme try { File themeFile = - new File(AppConstants.THEMES_DIR, AppPreferences.getDefaultMacroEditorTheme() + ".xml"); + new File(AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); Theme theme = Theme.load(new FileInputStream(themeFile)); theme.apply(textStatblockRSyntaxTextArea); @@ -1896,7 +1896,8 @@ public MTMultilineStringPopupPanel(String paramString) { // Set the color style via Theme try { File themeFile = - new File(AppConstants.THEMES_DIR, AppPreferences.getDefaultMacroEditorTheme() + ".xml"); + new File( + AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); Theme theme = Theme.load(new FileInputStream(themeFile)); theme.apply(j); @@ -1964,7 +1965,8 @@ private static class WordWrapCellRenderer extends RSyntaxTextArea implements Tab // Set the color style via Theme try { File themeFile = - new File(AppConstants.THEMES_DIR, AppPreferences.getDefaultMacroEditorTheme() + ".xml"); + new File( + AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); Theme theme = Theme.load(new FileInputStream(themeFile)); theme.apply(this); diff --git a/src/main/java/net/rptools/maptool/client/ui/tokenpanel/InitiativeListCellRenderer.java b/src/main/java/net/rptools/maptool/client/ui/tokenpanel/InitiativeListCellRenderer.java index bd51c8d96c..5f53679860 100644 --- a/src/main/java/net/rptools/maptool/client/ui/tokenpanel/InitiativeListCellRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/tokenpanel/InitiativeListCellRenderer.java @@ -349,7 +349,7 @@ public void paintIcon(Component c, Graphics g, int x, int y) { Graphics2D g2d = (Graphics2D) g; Stroke oldStroke = g2d.getStroke(); Color oldColor = g.getColor(); - g2d.setStroke(new BasicStroke(AppPreferences.getHaloLineWidth())); + g2d.setStroke(new BasicStroke(AppPreferences.haloLineWidth.get())); g.setColor(token.getHaloColor()); g2d.draw(new Rectangle2D.Double(x, y, ICON_SIZE, ICON_SIZE)); g2d.setStroke(oldStroke); diff --git a/src/main/java/net/rptools/maptool/client/ui/tokenpanel/InitiativePanel.java b/src/main/java/net/rptools/maptool/client/ui/tokenpanel/InitiativePanel.java index d9b1059099..fc5316a5f0 100644 --- a/src/main/java/net/rptools/maptool/client/ui/tokenpanel/InitiativePanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/tokenpanel/InitiativePanel.java @@ -75,22 +75,22 @@ public class InitiativePanel extends JPanel private final JList displayList; /** Flag indicating that token images are shown in the list. */ - private boolean showTokens = AppPreferences.getInitShowTokens(); + private boolean showTokens = AppPreferences.initiativePanelShowsTokenImage.get(); /** * Flag indicating that token states are shown in the list. Only valid if {@link #showTokens} is * true. */ - private boolean showTokenStates = AppPreferences.getInitShowTokenStates(); + private boolean showTokenStates = AppPreferences.initiativePanelShowsTokenState.get(); /** Flag indicating that initiative state is shown in the list. */ - private boolean showInitState = AppPreferences.getInitShowInitiative(); + private boolean showInitState = AppPreferences.initiativePanelShowsInitiative.get(); /** * Flag indicating that two lines are used for initiative stated. It is only valid if {@link * #showInitState} is true. */ - private boolean initStateSecondLine = AppPreferences.getInitShow2ndLine(); + private boolean initStateSecondLine = AppPreferences.initiativePanelShowsInitiativeOnLine2.get(); /** The zone data being displayed. */ private Zone zone; @@ -592,7 +592,8 @@ public void propertyChange(PropertyChangeEvent evt) { String s = I18N.getText("initPanel.displayMessage", t.getName()); if (InitiativeListModel.isTokenVisible(t, list.isHideNPC()) && t.getType() != Type.NPC - && AppPreferences.isShowInitGainMessage()) MapTool.addMessage(TextMessage.say(null, s)); + && AppPreferences.showInitiativeGainedMessage.get()) + MapTool.addMessage(TextMessage.say(null, s)); displayList.ensureIndexIsVisible(model.getDisplayIndex(list.getCurrent())); NEXT_ACTION.setEnabled( !isInitPanelButtonsDisabled() && hasOwnerPermission(list.getCurrentToken())); @@ -672,7 +673,7 @@ public void actionPerformed(ActionEvent e) { displayList.setCellRenderer( new InitiativeListCellRenderer( InitiativePanel.this)); // Regenerates the size of each row. - AppPreferences.setInitShowTokens(showTokens); + AppPreferences.initiativePanelShowsTokenImage.set(showTokens); } }; @@ -685,7 +686,7 @@ public void actionPerformed(ActionEvent e) { displayList.setCellRenderer( new InitiativeListCellRenderer( InitiativePanel.this)); // Regenerates the size of each row. - AppPreferences.setInitShowTokenStates(showTokenStates); + AppPreferences.initiativePanelShowsTokenState.set(showTokenStates); } }; @@ -698,7 +699,7 @@ public void actionPerformed(ActionEvent e) { displayList.setCellRenderer( new InitiativeListCellRenderer( InitiativePanel.this)); // Regenerates the size of each row. - AppPreferences.setInitShowInitiative(showInitState); + AppPreferences.initiativePanelShowsInitiative.set(showInitState); } }; @@ -711,7 +712,7 @@ public void actionPerformed(ActionEvent e) { displayList.setCellRenderer( new InitiativeListCellRenderer( InitiativePanel.this)); // Regenerates the size of each row. - AppPreferences.setInitShow2ndLine(initStateSecondLine); + AppPreferences.initiativePanelShowsInitiativeOnLine2.set(initStateSecondLine); } }; diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/FogRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/FogRenderer.java index 16c8b6f5eb..154200c656 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/FogRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/FogRenderer.java @@ -100,7 +100,7 @@ private void renderWorld(Graphics2D worldG, PlayerView view) { timer.start("renderFog-softFow"); if (!softFogArea.isEmpty()) { worldG.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); - worldG.setColor(new Color(0, 0, 0, AppPreferences.getFogOverlayOpacity())); + worldG.setColor(new Color(0, 0, 0, AppPreferences.fogOverlayOpacity.get())); worldG.fill(softFogArea); } timer.stop("renderFog-softFow"); diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/HaloRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/HaloRenderer.java index 55aa938bbc..36862c3d17 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/HaloRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/HaloRenderer.java @@ -40,7 +40,7 @@ public void setRenderer(ZoneRenderer zoneRenderer) { // Render Halos public void renderHalo(Graphics2D g2d, Token token, TokenLocation location) { if (token.hasHalo()) { - g2d.setStroke(new BasicStroke(AppPreferences.getHaloLineWidth())); + g2d.setStroke(new BasicStroke(AppPreferences.haloLineWidth.get())); g2d.setColor(token.getHaloColor()); g2d.draw(location.bounds); } diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/LightsRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/LightsRenderer.java index 971e2b3b68..08be6d4c99 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/LightsRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/LightsRenderer.java @@ -55,7 +55,7 @@ public void renderAuras(Graphics2D g2d, PlayerView view) { } final var lightBlending = - AlphaComposite.SrcOver.derive(AppPreferences.getAuraOverlayOpacity() / 255.0f); + AlphaComposite.SrcOver.derive(AppPreferences.auraOverlayOpacity.get() / 255.0f); final var overlayFillColor = new Color(0, 0, 0, 0); renderHelper.bufferedRender( @@ -83,7 +83,7 @@ public void renderLights(Graphics2D g2d, PlayerView view) { final var overlayBlending = switch (zone.getLightingStyle()) { case OVERTOP -> AlphaComposite.SrcOver.derive( - AppPreferences.getLightOverlayOpacity() / 255.f); + AppPreferences.lightOverlayOpacity.get() / 255.f); case ENVIRONMENTAL -> LightingComposite.OverlaidLights; }; final var overlayFillColor = diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/LumensRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/LumensRenderer.java index befe9e91e8..7a638a88cb 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/LumensRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/LumensRenderer.java @@ -59,7 +59,7 @@ public void render(Graphics2D g2d, PlayerView view) { private void renderWorld(Graphics2D worldG, PlayerView view) { var timer = CodeTimer.get(); - var overlayOpacity = AppPreferences.getLumensOverlayOpacity() / 255.0f; + var overlayOpacity = AppPreferences.lumensOverlayOpacity.get() / 255.0f; var visibleArea = zoneView.getVisibleArea(view); final var disjointLumensLevels = zoneView.getDisjointObscuredLumensLevels(view); @@ -116,7 +116,7 @@ private void renderWorld(Graphics2D worldG, PlayerView view) { } // Now draw borders around each region if configured. - final var borderThickness = AppPreferences.getLumensOverlayBorderThickness(); + final var borderThickness = AppPreferences.lumensOverlayBorderThickness.get(); if (borderThickness > 0) { worldG.setStroke( new BasicStroke((float) borderThickness, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/VisionOverlayRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/VisionOverlayRenderer.java index 6b537da5ee..c6e8bdfeb3 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/VisionOverlayRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/VisionOverlayRenderer.java @@ -86,7 +86,7 @@ private void renderWorld(Graphics2D worldG, PlayerView view, Token token) { worldG.draw(currentTokenVisionArea); Color visionColor = token.getVisionOverlayColor(); - if (visionColor == null && AppPreferences.getUseHaloColorOnVisionOverlay()) { + if (visionColor == null && AppPreferences.useHaloColorOnVisionOverlay.get()) { visionColor = token.getHaloColor(); } if (visionColor != null) { @@ -95,7 +95,7 @@ private void renderWorld(Graphics2D worldG, PlayerView view, Token token) { visionColor.getRed(), visionColor.getGreen(), visionColor.getBlue(), - AppPreferences.getHaloOverlayOpacity())); + AppPreferences.haloOverlayOpacity.get())); worldG.fill(currentTokenVisionArea); } } diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java index 18cd22ea0e..0bf71db39a 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java @@ -185,7 +185,8 @@ public ZoneRenderer(Zone zone) { this.fogRenderer = new FogRenderer(renderHelper, zone, zoneView); this.visionOverlayRenderer = new VisionOverlayRenderer(renderHelper, zone, zoneView); this.debugRenderer = new DebugRenderer(renderHelper); - repaintDebouncer = new DebounceExecutor(1000 / AppPreferences.getFrameRateCap(), this::repaint); + repaintDebouncer = + new DebounceExecutor(1000 / AppPreferences.frameRateCap.get(), this::repaint); setFocusable(true); selectionModel = new SelectionModel(zone); @@ -786,7 +787,8 @@ public void paintComponent(Graphics g) { if (MapTool.getFrame().areFullScreenToolsShown()) { noteVPos += 40; } - if (!AppPreferences.getMapVisibilityWarning() && (!zone.isVisible() && pl.isGMView())) { + if (!AppPreferences.mapVisibilityWarning.get() + && (!zone.isVisible() && pl.isGMView())) { GraphicsUtil.drawBoxedString( bufferG2d, I18N.getText("zone.map_not_visible"), getSize().width / 2, noteVPos); noteVPos += 20; @@ -1268,7 +1270,7 @@ protected void renderBoard(Graphics2D g, PlayerView view) { } if (drawBackground) { Graphics2D bbg = backbuffer.createGraphics(); - AppPreferences.getRenderQuality().setRenderingHints(bbg); + AppPreferences.renderQuality.get().setRenderingHints(bbg); // Background texture Paint paint = @@ -2097,7 +2099,7 @@ protected void renderTokens( Area visibleArea = new Area(g.getClipBounds()); visibleArea.intersect(visibleScreenArea); clippedG.setClip(new GeneralPath(visibleArea)); - AppPreferences.getRenderQuality().setRenderingHints(clippedG); + AppPreferences.renderQuality.get().setRenderingHints(clippedG); } timer.stop("createClip"); @@ -2300,7 +2302,7 @@ protected void renderTokens( tokenG = (Graphics2D) clippedG.create(); } else { tokenG = (Graphics2D) g.create(); - AppPreferences.getRenderQuality().setRenderingHints(tokenG); + AppPreferences.renderQuality.get().setRenderingHints(tokenG); } // Previous path @@ -2502,7 +2504,7 @@ protected void renderTokens( Token.TokenShape tokenType = token.getShape(); switch (tokenType) { case FIGURE: - if (token.getHasImageTable() && AppPreferences.getForceFacingArrow() == false) { + if (token.getHasImageTable() && !AppPreferences.forceFacingArrow.get()) { break; } Shape arrow = getFigureFacingArrow(token.getFacing(), footprintBounds.width / 2); @@ -2526,7 +2528,7 @@ protected void renderTokens( tokenG.translate(-fx, -fy); break; case TOP_DOWN: - if (AppPreferences.getForceFacingArrow() == false) { + if (!AppPreferences.forceFacingArrow.get()) { break; } case CIRCLE: @@ -2833,7 +2835,7 @@ protected void renderTokens( // Stacks // TODO: find a cleaner way to indicate token layer if (!tokenList.isEmpty() && tokenList.get(0).getLayer().isTokenLayer()) { - boolean hideTSI = AppPreferences.getHideTokenStackIndicator(); + boolean hideTSI = AppPreferences.hideTokenStackIndicator.get(); if (tokenStackMap != null && !hideTSI) { // FIXME Needed to prevent NPE but how can it be null? for (Token token : tokenStackMap.keySet()) { @@ -3224,7 +3226,7 @@ public void addTokens( // Get the snap to grid value for the current prefs and abilities token.setSnapToGrid( - gridCaps.isSnapToGridSupported() && AppPreferences.getTokensStartSnapToGrid()); + gridCaps.isSnapToGridSupported() && AppPreferences.tokensStartSnapToGrid.get()); if (token.isSnapToGrid()) { zp = zone.getGrid().convert(zone.getGrid().convert(zp)); } @@ -3257,17 +3259,17 @@ public void addTokens( switch (getActiveLayer()) { case TOKEN: // Players can't drop invisible tokens - token.setVisible(!isGM || AppPreferences.getNewTokensVisible()); - if (AppPreferences.getTokensStartFreesize()) { + token.setVisible(!isGM || AppPreferences.newTokensVisible.get()); + if (AppPreferences.tokensStartFreesize.get()) { token.setSnapToScale(false); } break; case BACKGROUND: token.setShape(Token.TokenShape.TOP_DOWN); - token.setSnapToScale(!AppPreferences.getBackgroundsStartFreesize()); - token.setSnapToGrid(AppPreferences.getBackgroundsStartSnapToGrid()); - token.setVisible(AppPreferences.getNewBackgroundsVisible()); + token.setSnapToScale(!AppPreferences.backgroundsStartFreesize.get()); + token.setSnapToGrid(AppPreferences.backgroundsStartSnapToGrid.get()); + token.setVisible(AppPreferences.newBackgroundsVisible.get()); // Center on drop point if (!token.isSnapToScale() && !token.isSnapToGrid()) { @@ -3278,9 +3280,9 @@ public void addTokens( case OBJECT: token.setShape(Token.TokenShape.TOP_DOWN); - token.setSnapToScale(!AppPreferences.getObjectsStartFreesize()); - token.setSnapToGrid(AppPreferences.getObjectsStartSnapToGrid()); - token.setVisible(AppPreferences.getNewObjectsVisible()); + token.setSnapToScale(!AppPreferences.objectsStartFreesize.get()); + token.setSnapToGrid(AppPreferences.objectsStartSnapToGrid.get()); + token.setVisible(AppPreferences.newObjectsVisible.get()); // Center on drop point if (!token.isSnapToScale() && !token.isSnapToGrid()) { @@ -3305,7 +3307,7 @@ public void addTokens( token.setName(MapToolUtil.nextTokenId(zone, token, tokenNameUsed != null)); if (getActiveLayer() == Zone.Layer.TOKEN) { - if (AppPreferences.getShowDialogOnNewToken() || showDialog) { + if (AppPreferences.showDialogOnNewToken.get() || showDialog) { NewTokenDialog dialog = new NewTokenDialog(token, dropPoint.x, dropPoint.y); dialog.showDialog(); if (!dialog.isSuccess()) { diff --git a/src/main/java/net/rptools/maptool/model/CampaignProperties.java b/src/main/java/net/rptools/maptool/model/CampaignProperties.java index 4560ef0f9c..31644f8973 100644 --- a/src/main/java/net/rptools/maptool/model/CampaignProperties.java +++ b/src/main/java/net/rptools/maptool/model/CampaignProperties.java @@ -78,10 +78,11 @@ public class CampaignProperties { private Map characterSheets = new HashMap<>(); /** Flag indicating that owners have special permissions */ - private boolean initiativeOwnerPermissions = AppPreferences.getInitOwnerPermissions(); + private boolean initiativeOwnerPermissions = + AppPreferences.initiativePanelAllowsOwnerPermissions.get(); /** Flag indicating that owners can only move tokens when they have initiative */ - private boolean initiativeMovementLock = AppPreferences.getInitLockMovement(); + private boolean initiativeMovementLock = AppPreferences.initiativeMovementLocked.get(); /** Whether the default initiative sort order is reversed */ private boolean initiativeUseReverseSort = false; diff --git a/src/main/java/net/rptools/maptool/model/Grid.java b/src/main/java/net/rptools/maptool/model/Grid.java index 91d3754509..02d6c0a39b 100644 --- a/src/main/java/net/rptools/maptool/model/Grid.java +++ b/src/main/java/net/rptools/maptool/model/Grid.java @@ -75,7 +75,7 @@ public abstract class Grid implements Cloneable { private int size; public Grid() { - setSize(AppPreferences.getDefaultGridSize()); + setSize(AppPreferences.defaultGridSize.get()); } public Grid(Grid grid) { @@ -936,7 +936,7 @@ protected Set generateRadius(int minRadius, int maxRadius) { */ protected WalkerMetric getCurrentMetric() { return MapTool.isPersonalServer() - ? AppPreferences.getMovementMetric() + ? AppPreferences.movementMetric.get() : MapTool.getServerPolicy().getMovementMetric(); } diff --git a/src/main/java/net/rptools/maptool/model/HeroLabData.java b/src/main/java/net/rptools/maptool/model/HeroLabData.java index 7d278c2996..f13358f655 100644 --- a/src/main/java/net/rptools/maptool/model/HeroLabData.java +++ b/src/main/java/net/rptools/maptool/model/HeroLabData.java @@ -291,7 +291,7 @@ public void setPortfolioPath(String portfolioPath) { } public File getPortfolioFile() { - String fileSyncPath = AppPreferences.getFileSyncPath(); + String fileSyncPath = AppPreferences.fileSyncPath.get(); if (portfolioPath == null || portfolioPath.isEmpty() || fileSyncPath.isEmpty()) { return portfolioFile; @@ -305,10 +305,10 @@ public void setPortfolioFile(File portfolioFile) { this.portfolioFile = portfolioFile; lastModified = getPortfolioLastModified(); - if (!AppPreferences.getFileSyncPath().isEmpty()) { + if (!AppPreferences.fileSyncPath.get().isEmpty()) { try { portfolioPath = - Paths.get(AppPreferences.getFileSyncPath()) + Paths.get(AppPreferences.fileSyncPath.get()) .relativize(portfolioFile.toPath()) .toString(); } catch (IllegalArgumentException e) { @@ -316,7 +316,7 @@ public void setPortfolioFile(File portfolioFile) { "Unable to relativize paths for: [" + portfolioFile + "] [" - + AppPreferences.getFileSyncPath() + + AppPreferences.fileSyncPath.get() + "]"); portfolioPath = ""; } diff --git a/src/main/java/net/rptools/maptool/model/InitiativeList.java b/src/main/java/net/rptools/maptool/model/InitiativeList.java index 85f4f13c51..b1f004f07c 100644 --- a/src/main/java/net/rptools/maptool/model/InitiativeList.java +++ b/src/main/java/net/rptools/maptool/model/InitiativeList.java @@ -79,7 +79,7 @@ public class InitiativeList implements Serializable { private boolean fullUpdate; /** Hide all of the NPC's from the players. */ - private boolean hideNPC = AppPreferences.getInitHideNpcs(); + private boolean hideNPC = AppPreferences.initiativePanelHidesNpcs.get(); /*--------------------------------------------------------------------------------------------- * Class Variables diff --git a/src/main/java/net/rptools/maptool/model/IsometricGrid.java b/src/main/java/net/rptools/maptool/model/IsometricGrid.java index 319e6bb571..5242e5e0c1 100644 --- a/src/main/java/net/rptools/maptool/model/IsometricGrid.java +++ b/src/main/java/net/rptools/maptool/model/IsometricGrid.java @@ -200,7 +200,7 @@ public boolean useMetric() { public ZoneWalker createZoneWalker() { WalkerMetric metric = MapTool.isPersonalServer() - ? AppPreferences.getMovementMetric() + ? AppPreferences.movementMetric.get() : MapTool.getServerPolicy().getMovementMetric(); return new AStarSquareEuclideanWalker(getZone(), metric); } diff --git a/src/main/java/net/rptools/maptool/model/MacroButtonProperties.java b/src/main/java/net/rptools/maptool/model/MacroButtonProperties.java index c90382f157..b029a2145f 100644 --- a/src/main/java/net/rptools/maptool/model/MacroButtonProperties.java +++ b/src/main/java/net/rptools/maptool/model/MacroButtonProperties.java @@ -73,7 +73,7 @@ public class MacroButtonProperties implements Comparable private @Nonnull String fontSize = "1.00em"; private @Nonnull String minWidth = ""; private @Nonnull String maxWidth = ""; - private @Nonnull Boolean allowPlayerEdits = AppPreferences.getAllowPlayerMacroEditsDefault(); + private @Nonnull Boolean allowPlayerEdits = AppPreferences.allowPlayerMacroEditsDefault.get(); private @Nonnull String toolTip = ""; private @Nonnull Boolean displayHotKey = true; @@ -174,7 +174,7 @@ public MacroButtonProperties(int index) { setMaxWidth(""); setTokenId((GUID) null); setSaveLocation(""); - setAllowPlayerEdits(AppPreferences.getAllowPlayerMacroEditsDefault()); + setAllowPlayerEdits(AppPreferences.allowPlayerMacroEditsDefault.get()); setDisplayHotKey(true); setCompareGroup(true); setCompareSortPrefix(true); @@ -190,7 +190,7 @@ public MacroButtonProperties(String panelClass, int index, String group) { this(index); setSaveLocation(panelClass); setGroup(group); - setAllowPlayerEdits(AppPreferences.getAllowPlayerMacroEditsDefault()); + setAllowPlayerEdits(AppPreferences.allowPlayerMacroEditsDefault.get()); setDisplayHotKey(true); setCompareGroup(true); setCompareSortPrefix(true); @@ -208,7 +208,7 @@ public MacroButtonProperties(Token token, int index, String group) { setSaveLocation("Token"); setTokenId(token); setGroup(group); - setAllowPlayerEdits(AppPreferences.getAllowPlayerMacroEditsDefault()); + setAllowPlayerEdits(AppPreferences.allowPlayerMacroEditsDefault.get()); setDisplayHotKey(true); setCompareGroup(true); setCompareSortPrefix(true); @@ -815,7 +815,7 @@ public void reset() { fontSize = "1.00em"; minWidth = ""; maxWidth = ""; - allowPlayerEdits = AppPreferences.getAllowPlayerMacroEditsDefault(); + allowPlayerEdits = AppPreferences.allowPlayerMacroEditsDefault.get(); toolTip = ""; } @@ -1058,7 +1058,7 @@ public Object readResolve() { compareApplyToSelectedTokens = true; } if (allowPlayerEdits == null) { - allowPlayerEdits = AppPreferences.getAllowPlayerMacroEditsDefault(); + allowPlayerEdits = AppPreferences.allowPlayerMacroEditsDefault.get(); } if (macroUUID == null) { macroUUID = UUID.randomUUID().toString(); diff --git a/src/main/java/net/rptools/maptool/model/SquareGrid.java b/src/main/java/net/rptools/maptool/model/SquareGrid.java index 5c6e25cd07..34c13aa96a 100644 --- a/src/main/java/net/rptools/maptool/model/SquareGrid.java +++ b/src/main/java/net/rptools/maptool/model/SquareGrid.java @@ -290,7 +290,7 @@ public CellPoint convert(ZonePoint zp) { public ZoneWalker createZoneWalker() { WalkerMetric metric = MapTool.isPersonalServer() - ? AppPreferences.getMovementMetric() + ? AppPreferences.movementMetric.get() : MapTool.getServerPolicy().getMovementMetric(); return new AStarSquareEuclideanWalker(getZone(), metric); } diff --git a/src/main/java/net/rptools/maptool/model/ZoneFactory.java b/src/main/java/net/rptools/maptool/model/ZoneFactory.java index e28514099a..7a18412c0f 100644 --- a/src/main/java/net/rptools/maptool/model/ZoneFactory.java +++ b/src/main/java/net/rptools/maptool/model/ZoneFactory.java @@ -57,15 +57,15 @@ public static Zone createZone() { zone.setBackgroundPaint(new DrawableTexturePaint(defaultImageId)); zone.setFogPaint(new DrawableColorPaint(Color.black)); - zone.setVisible(AppPreferences.getNewMapsVisible()); - zone.setHasFog(AppPreferences.getNewMapsHaveFOW()); - zone.setUnitsPerCell(AppPreferences.getDefaultUnitsPerCell()); - zone.setTokenVisionDistance(AppPreferences.getDefaultVisionDistance()); - zone.setVisionType(AppPreferences.getDefaultVisionType()); + zone.setVisible(AppPreferences.newMapsVisible.get()); + zone.setHasFog(AppPreferences.newMapsHaveFow.get()); + zone.setUnitsPerCell(AppPreferences.defaultUnitsPerCell.get()); + zone.setTokenVisionDistance(AppPreferences.defaultVisionDistance.get()); + zone.setVisionType(AppPreferences.defaultVisionType.get()); - zone.setGrid(GridFactory.createGrid(AppPreferences.getDefaultGridType())); - zone.setGridColor(AppPreferences.getDefaultGridColor().getRGB()); - zone.getGrid().setSize(AppPreferences.getDefaultGridSize()); + zone.setGrid(GridFactory.createGrid(AppPreferences.defaultGridType.get())); + zone.setGridColor(AppPreferences.defaultGridColor.get().getRGB()); + zone.getGrid().setSize(AppPreferences.defaultGridSize.get()); zone.getGrid().setOffset(0, 0); return zone; diff --git a/src/main/java/net/rptools/maptool/model/player/LocalPlayer.java b/src/main/java/net/rptools/maptool/model/player/LocalPlayer.java index db8d24e005..5d908166a9 100644 --- a/src/main/java/net/rptools/maptool/model/player/LocalPlayer.java +++ b/src/main/java/net/rptools/maptool/model/player/LocalPlayer.java @@ -28,7 +28,7 @@ public class LocalPlayer extends Player { private CipherUtil.Key password; public LocalPlayer() throws NoSuchAlgorithmException, InvalidKeySpecException { - this(AppPreferences.getDefaultUserName(), Role.GM, ""); + this(AppPreferences.defaultUserName.get(), Role.GM, ""); } public LocalPlayer(String name, Role role, String plainTextPassword) 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 b66160ec6f..47e19f7d85 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 @@ -187,7 +187,7 @@ public StatSheetContext(Token token, Player player, StatSheetLocation location) notesType = AppUtil.playerOwns(token) ? token.getNotesType() : null; speechName = token.getSpeechName(); - if (AppPreferences.getShowPortrait()) { + if (AppPreferences.showPortrait.get()) { imageAsset = token.getImageAssetId(); portraitAsset = token.getPortraitImage(); } else { @@ -238,7 +238,7 @@ public StatSheetContext(Token token, Player player, StatSheetLocation location) var image = ImageManager.getImage(token.getImageAssetId()); dim = new Dimension(image.getWidth(), image.getHeight()); } - SwingUtil.constrainTo(dim, AppPreferences.getPortraitSize()); + SwingUtil.constrainTo(dim, AppPreferences.portraitSize.get()); portraitWidth = dim.width; portraitHeight = dim.height; diff --git a/src/main/java/net/rptools/maptool/server/ServerPolicy.java b/src/main/java/net/rptools/maptool/server/ServerPolicy.java index b4cdb80530..11300be34b 100644 --- a/src/main/java/net/rptools/maptool/server/ServerPolicy.java +++ b/src/main/java/net/rptools/maptool/server/ServerPolicy.java @@ -43,14 +43,14 @@ public class ServerPolicy { private boolean hidemapselectui; private boolean disablePlayerAssetPanel; - private boolean useAstarPathfinding = AppPreferences.isUsingAstarPathfinding(); - private boolean vblBlocksMove = AppPreferences.getVblBlocksMove(); + private boolean useAstarPathfinding = AppPreferences.pathfindingEnabled.get(); + private boolean vblBlocksMove = AppPreferences.pathfindingBlockedByVbl.get(); public ServerPolicy() { // Default tool tip usage for inline rolls to user preferences. - useToolTipsForDefaultRollFormat = AppPreferences.getUseToolTipForInlineRoll(); + useToolTipsForDefaultRollFormat = AppPreferences.useToolTipForInlineRoll.get(); // Default movement metric from preferences - movementMetric = AppPreferences.getMovementMetric(); + movementMetric = AppPreferences.movementMetric.get(); } public ServerPolicy(ServerPolicy other) { @@ -290,7 +290,7 @@ public JsonObject toJSON() { getDisablePlayerAssetPanel() ? BigDecimal.ONE : BigDecimal.ZERO); WalkerMetric metric = - MapTool.isPersonalServer() ? AppPreferences.getMovementMetric() : getMovementMetric(); + MapTool.isPersonalServer() ? AppPreferences.movementMetric.get() : getMovementMetric(); sinfo.addProperty("movement metric", metric.name()); sinfo.addProperty("using ai", isUsingAstarPathfinding() ? BigDecimal.ONE : BigDecimal.ZERO); diff --git a/src/main/java/net/rptools/maptool/util/ExtractHeroLab.java b/src/main/java/net/rptools/maptool/util/ExtractHeroLab.java index fe1165bb90..dde1a755f2 100644 --- a/src/main/java/net/rptools/maptool/util/ExtractHeroLab.java +++ b/src/main/java/net/rptools/maptool/util/ExtractHeroLab.java @@ -104,7 +104,7 @@ public ExtractHeroLab(File heroLabPortfolio, boolean forceRescan) { private File validatePortfolioFile(File heroLabPortfolio) { // If unable to find file, prompt user to point to new location or fail if (!heroLabPortfolio.exists()) { - JFileChooser fileChooser = new JFileChooser(AppPreferences.getFileSyncPath()); + JFileChooser fileChooser = new JFileChooser(AppPreferences.fileSyncPath.get()); fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); FileNameExtensionFilter filter = new FileNameExtensionFilter("Hero Lab Portfolio", "por"); fileChooser.setFileFilter(filter); diff --git a/src/main/java/net/rptools/maptool/util/MessageUtil.java b/src/main/java/net/rptools/maptool/util/MessageUtil.java index 613ca43d86..25cfeecfa7 100644 --- a/src/main/java/net/rptools/maptool/util/MessageUtil.java +++ b/src/main/java/net/rptools/maptool/util/MessageUtil.java @@ -137,7 +137,7 @@ private static String getAvatarMessage(String msg, Token token, String identity) sb.append(""); - if (AppPreferences.getShowAvatarInChat()) { + if (AppPreferences.showAvatarInChat.get()) { if (token == null && MapTool.getFrame().getCommandPanel().isImpersonating()) { GUID guid = MapTool.getFrame().getCommandPanel().getIdentityGUID(); if (guid != null) diff --git a/src/main/java/net/rptools/maptool/util/PersistenceUtil.java b/src/main/java/net/rptools/maptool/util/PersistenceUtil.java index d570cecd72..77fe44f73b 100644 --- a/src/main/java/net/rptools/maptool/util/PersistenceUtil.java +++ b/src/main/java/net/rptools/maptool/util/PersistenceUtil.java @@ -660,7 +660,7 @@ public static void saveToken(Token token, File file, boolean doWait) throws IOEx BufferedImage thumb = new BufferedImage(sz.width, sz.height, BufferedImage.TRANSLUCENT); Graphics2D g = thumb.createGraphics(); - AppPreferences.getRenderQuality().setShrinkRenderingHints(g); + AppPreferences.renderQuality.get().setShrinkRenderingHints(g); g.drawImage(image, 0, 0, sz.width, sz.height, null); g.dispose(); @@ -672,7 +672,7 @@ public static void saveToken(Token token, File file, boolean doWait) throws IOEx Math.min(image.getHeight(), MapTool.getThumbnailSize().height)); BufferedImage thumbLarge = new BufferedImage(sz.width, sz.height, BufferedImage.TRANSLUCENT); g = thumbLarge.createGraphics(); - AppPreferences.getRenderQuality().setShrinkRenderingHints(g); + AppPreferences.renderQuality.get().setShrinkRenderingHints(g); g.drawImage(image, 0, 0, sz.width, sz.height, null); g.dispose(); diff --git a/src/main/java/net/rptools/maptool/util/UPnPUtil.java b/src/main/java/net/rptools/maptool/util/UPnPUtil.java index 984c53cf5f..34221c310e 100644 --- a/src/main/java/net/rptools/maptool/util/UPnPUtil.java +++ b/src/main/java/net/rptools/maptool/util/UPnPUtil.java @@ -77,7 +77,7 @@ public static boolean findIGDs() { InternetGatewayDevice[] thisNI; thisNI = InternetGatewayDevice.getDevices( - AppPreferences.getUpnpDiscoveryTimeout(), + AppPreferences.upnpDiscoveryTimeout.get(), Discovery.DEFAULT_TTL, Discovery.DEFAULT_MX, ni); From 074e87918e89bd0319c3d9006db733bc51252762 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:43:21 +0000 Subject: [PATCH 054/146] Bump com.twelvemonkeys.imageio:imageio-jpeg from 3.10.1 to 3.12.0 Bumps com.twelvemonkeys.imageio:imageio-jpeg from 3.10.1 to 3.12.0. --- updated-dependencies: - dependency-name: com.twelvemonkeys.imageio:imageio-jpeg dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 516b867361..d3bfe5e7d6 100644 --- a/build.gradle +++ b/build.gradle @@ -328,7 +328,7 @@ dependencies { // Image processing lib implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-core', version: '3.10.1' // https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-core - implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-jpeg', version: '3.10.1' // https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-core + implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-jpeg', version: '3.12.0' // https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-core implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-psd', version: '3.10.1' // https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-psd implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-tiff', version: '3.11.0' implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-batik', version: '3.10.1' From 438c2c7853a5c4c77862ba4f172db26df406670b Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:49:37 +0800 Subject: [PATCH 055/146] Could not find a definite point where deleting a macro updated the hotkey list. Added updateKeyStrokes call to both macro deleting methods. --- .../net/rptools/maptool/client/functions/MacroFunctions.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java b/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java index 89a9a1011d..93a23a1e09 100644 --- a/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java @@ -943,6 +943,7 @@ private String removeMacro(int index, boolean gmPanel) throws ParserException { } AbstractMacroPanel macroPanel = gmPanel ? MapTool.getFrame().getGmPanel() : MapTool.getFrame().getCampaignPanel(); + MapTool.getFrame().updateKeyStrokes(); // ensure hotkeys are updated macroPanel.reset(); return "Removed macro button " + label @@ -970,6 +971,7 @@ private String removeMacro(int index, Token token) throws ParserException { String label = mbp.getLabel(); MapTool.serverCommand().updateTokenProperty(token, Token.Update.deleteMacro, index); + MapTool.getFrame().updateKeyStrokes(); // ensure hotkeys are updated return "Removed macro button " + label + "(index = " + index + ") from " + token.getName(); } From bcbd7f4c49122d81c9b7a0cd719e5318cb3d853b Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:10:56 +0800 Subject: [PATCH 056/146] New translations i18n.properties (Danish) --- .../resources/net/rptools/maptool/language/i18n_da_DK.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_da_DK.properties b/src/main/resources/net/rptools/maptool/language/i18n_da_DK.properties index 447a6a1928..52081602a2 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_da_DK.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_da_DK.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From 66a30a5a42691073ce0550ba3767dd8dd59a29c8 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:10:58 +0800 Subject: [PATCH 057/146] New translations i18n.properties (French) --- .../resources/net/rptools/maptool/language/i18n_fr_FR.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_fr_FR.properties b/src/main/resources/net/rptools/maptool/language/i18n_fr_FR.properties index 57c9bd526e..97e09ff43b 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_fr_FR.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_fr_FR.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From 1ee84e2880fcee85fa2c6c69b314b9a2aab87a21 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:00 +0800 Subject: [PATCH 058/146] New translations i18n.properties (Spanish) --- .../resources/net/rptools/maptool/language/i18n_es_ES.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_es_ES.properties b/src/main/resources/net/rptools/maptool/language/i18n_es_ES.properties index 3552792f8b..c2ecc90c4e 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_es_ES.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_es_ES.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From d98f733ee8c11a594fc5a44162bff99aecc893be Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:02 +0800 Subject: [PATCH 059/146] New translations i18n.properties (Czech) --- .../resources/net/rptools/maptool/language/i18n_cs_CZ.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_cs_CZ.properties b/src/main/resources/net/rptools/maptool/language/i18n_cs_CZ.properties index da658c81db..2c86e69cec 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_cs_CZ.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_cs_CZ.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From 065e6c7d21c62f3e082ce1c5167d5c02e5e3a325 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:04 +0800 Subject: [PATCH 060/146] New translations i18n.properties (German) --- .../resources/net/rptools/maptool/language/i18n_de_DE.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties b/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties index fbc7d3fb33..892f52d239 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Vordefinitionen importieren CampaignPropertiesDialog.button.importPredefined.tooltip = Vordefinierte Eigenschaften und Einstellungen für das ausgewählte System importieren CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Kampagneneigenschaften in Datei exportieren +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System für das Eigenschaften importiert werden CampaignPropertiesDialog.button.import = Vordefinitionen importieren campaignPropertiesDialog.newTokenTypeName = Name des neuen Spielmarkentyps From f8b50f4689a3d630502272e73ff750518263bc05 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:06 +0800 Subject: [PATCH 061/146] New translations i18n.properties (Italian) --- .../resources/net/rptools/maptool/language/i18n_it_IT.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_it_IT.properties b/src/main/resources/net/rptools/maptool/language/i18n_it_IT.properties index 88067700b0..5c1eec73ba 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_it_IT.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_it_IT.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From 45b706571d758095c395a52438d543aebcd8d2f2 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:08 +0800 Subject: [PATCH 062/146] New translations i18n.properties (Japanese) --- .../resources/net/rptools/maptool/language/i18n_ja_JP.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties b/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties index 6cdb10a17d..40c5d8c3bb 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = 定義済み設定を取り CampaignPropertiesDialog.button.importPredefined.tooltip = 選択したシステム向けに予め定義された特性値と設定を取り込む CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = キャンペーン設定をファイルに書き出す +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = 取り込む特性値のシステム名称 CampaignPropertiesDialog.button.import = 定義済み設定を取り込む campaignPropertiesDialog.newTokenTypeName = 新しいトークン種別の名称を入力する From f4181d06bd6360264b1ae1f37d31daebcf0c3a18 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:09 +0800 Subject: [PATCH 063/146] New translations i18n.properties (Dutch) --- .../resources/net/rptools/maptool/language/i18n_nl_NL.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_nl_NL.properties b/src/main/resources/net/rptools/maptool/language/i18n_nl_NL.properties index 3346d90e02..b4809fbf56 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_nl_NL.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_nl_NL.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From d8752d939ce2c1efc000772a57bfd13ac2aa88fb Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:12 +0800 Subject: [PATCH 064/146] New translations i18n.properties (Polish) --- .../resources/net/rptools/maptool/language/i18n_pl_PL.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_pl_PL.properties b/src/main/resources/net/rptools/maptool/language/i18n_pl_PL.properties index 561e65472e..418d3b2f85 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_pl_PL.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_pl_PL.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Wprowadź nazwę nowego typu tokenu From 4f826755fdd0475b7c0a0d9be38a1c4f2130427d Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:14 +0800 Subject: [PATCH 065/146] New translations i18n.properties (Russian) --- .../resources/net/rptools/maptool/language/i18n_ru_RU.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_ru_RU.properties b/src/main/resources/net/rptools/maptool/language/i18n_ru_RU.properties index 3bbb0f18ab..feea347de8 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_ru_RU.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_ru_RU.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From 97321d5d393baf4db86f37e6eadde22e609b7cc8 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:16 +0800 Subject: [PATCH 066/146] New translations i18n.properties (Swedish) --- .../resources/net/rptools/maptool/language/i18n_sv_SE.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_sv_SE.properties b/src/main/resources/net/rptools/maptool/language/i18n_sv_SE.properties index 14ab46a173..fe994dac94 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_sv_SE.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_sv_SE.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From 0f1d3965a35c5d81d16ee40d25b5698f1d4aa832 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:18 +0800 Subject: [PATCH 067/146] New translations i18n.properties (Ukrainian) --- .../resources/net/rptools/maptool/language/i18n_uk_UA.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_uk_UA.properties b/src/main/resources/net/rptools/maptool/language/i18n_uk_UA.properties index b926633fa1..78cc69c18f 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_uk_UA.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_uk_UA.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From fdb0a08ebd10e6af6ae3093afbf8487c15bb038f Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:19 +0800 Subject: [PATCH 068/146] New translations i18n.properties (Chinese Simplified) --- .../resources/net/rptools/maptool/language/i18n_zh_CN.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_zh_CN.properties b/src/main/resources/net/rptools/maptool/language/i18n_zh_CN.properties index 718f1b8cb7..4ca9d5191a 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_zh_CN.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_zh_CN.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From 37da0ad9c57b9c1dbe32b21b995bd9ddaad90d5c Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:21 +0800 Subject: [PATCH 069/146] New translations i18n.properties (Portuguese, Brazilian) --- .../resources/net/rptools/maptool/language/i18n_pt_BR.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_pt_BR.properties b/src/main/resources/net/rptools/maptool/language/i18n_pt_BR.properties index 60bfe62d34..213a6f81f7 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_pt_BR.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_pt_BR.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties from a file CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import Predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From 35606a9fc0f87f3f6b58506b8afd5b2c89a478ae Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:23 +0800 Subject: [PATCH 070/146] New translations i18n.properties (English, Australia) --- .../resources/net/rptools/maptool/language/i18n_en_AU.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties b/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties index 161c10b90f..3c51b44586 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From fa3e0825b2c38854ea285b70e47a802764360f40 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:24 +0800 Subject: [PATCH 071/146] New translations i18n.properties (English, United Kingdom) --- .../resources/net/rptools/maptool/language/i18n_en_GB.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties b/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties index 74702102fa..ccfdf50ff3 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From 784ecbc1cbe92a96b7e19caba7a5ea40f4c6b475 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:11:26 +0800 Subject: [PATCH 072/146] New translations i18n.properties (Sinhala) --- .../resources/net/rptools/maptool/language/i18n_si_LK.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_si_LK.properties b/src/main/resources/net/rptools/maptool/language/i18n_si_LK.properties index a3a385c70e..0945e5e313 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_si_LK.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_si_LK.properties @@ -810,6 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From fa7118fd7a70d9ec2b7e4d87560bc8a4ee397a27 Mon Sep 17 00:00:00 2001 From: cwisniew Date: Thu, 10 Oct 2024 18:49:36 +1030 Subject: [PATCH 073/146] Formatting fix --- .../maptool/client/ui/addon/AddOnLibrariesDialogView.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index 915becdb50..5ce4cc77c2 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -314,7 +314,8 @@ public void windowClosing(WindowEvent e) { } catch (IOException ex) { // do nothing } - AppPreferences.externalAddOnLibrariesEnabled.set(enableExternalAddOnCheckBox.isSelected()); + AppPreferences.externalAddOnLibrariesEnabled.set( + enableExternalAddOnCheckBox.isSelected()); }); browseButton.addActionListener( From 5ced5080e20a29f000129c742c056ac59546df26 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:20:13 +0800 Subject: [PATCH 074/146] Update source file i18n.properties --- src/main/resources/net/rptools/maptool/language/i18n.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 2a52ce3242..e28a6e524e 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -812,6 +812,7 @@ CampaignPropertiesDialog.button.importPredefined = Import Predefined CampaignPropertiesDialog.button.importPredefined.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.import.tooltip = Import predefined properties from a file CampaignPropertiesDialog.button.export.tooltip = Export campaign properties to file +CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. CampaignPropertiesDialog.combo.importPredefined.tooltip = System to import properties for CampaignPropertiesDialog.button.import = Import Predefined campaignPropertiesDialog.newTokenTypeName = Enter the name for the new token type From cc98fefb112eae90ad03ddddaf58a5492e9e9871 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Sat, 5 Oct 2024 14:15:04 -0700 Subject: [PATCH 075/146] Decompose `Grid.getShapedArea()` so we can reuse logic between grids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now calculate these things separately: 1. Which way the token is facing on the grid. 2. The shape of the light itself. 3. The shape of the token's footprint contribution to the light (for cones). With this we gain the following benefits: 1. `IsometricGrid` can reuse the main shape logic and just transform the result. No more duplicated logic. 2. The code should be more approachable as each method does One Thing™. Lights on isometric grids act literally the same as for square grids, just rotated and foreshortened. This fixes some related bugs, e.g., beams not getting visually shorted when vertical. It also means iso grids now have a full set of accurate lights available, e.g., hexes no longer default to ellipses. --- .../java/net/rptools/maptool/model/Grid.java | 166 +++++++++++++----- .../rptools/maptool/model/IsometricGrid.java | 117 ++++-------- .../java/net/rptools/maptool/model/Zone.java | 16 +- 3 files changed, 154 insertions(+), 145 deletions(-) diff --git a/src/main/java/net/rptools/maptool/model/Grid.java b/src/main/java/net/rptools/maptool/model/Grid.java index 3e6de5ee8c..80bba9cb92 100644 --- a/src/main/java/net/rptools/maptool/model/Grid.java +++ b/src/main/java/net/rptools/maptool/model/Grid.java @@ -387,50 +387,57 @@ public void setSize(int size) { fireGridChanged(); } + // region Light shapes + /** - * Called by SightType and Light class to return a vision area based upon a specified distance + * Get the grid-relative angle of the token based on its facing. * - * @param shape CIRCLE, GRID, SQUARE, CONE or LINE - * @param token Used to position the shape and to provide footprint - * @param range As specified in the vision or light definition - * @param arcAngle Only used by cone - * @param offsetAngle Arc distance from facing, only used by cone - * @param scaleWithToken used to increase the area based on token footprint - * @return Area + *

This is used to rotate cones and beams according to the on-grid angle. The result is the + * number of clockwise degrees measured from the positive x-axis of the grid. + * + *

This method exists because {@link net.rptools.maptool.model.Token#getFacing()} is a measure + * of the on-screen angle of the token's facing, i.e., how many degrees from the positive x-axis + * of the screen. For most grids this is the same as measuring the number of degress from the + * positive x-axis of the grid, which is what is should be. + * + *

Things are different for isometric grids. Since they are rotated, the on-screen facing does + * not agree with the on-grid facing - there is a 45° offset. When building shapes, we need the + * on-grid facing, not the on-screen facing. This method allows isometric grids to override the + * default behaviour so that an on-grid facing is provided. + * + * @param token The token whose facing needs to be determined. + * @return The direction the token is facing, in clockwise degrees from the positive x-axis of the + * grid. */ - public @Nonnull Area getShapedArea( + protected int getTokenFacingAngleRelativeToGridAxis(Token token) { + return -token.getFacing(); + } + + /** + * Get the main area for a given light shape type. + * + *

This method expressly does not add in the footprint bit that cone lights are expected to + * have. This part cannot be freely transformed, so it is done separately in {@link + * #getFootprintShapedAreaForCone(java.awt.Rectangle)}. + * + * @param shape The shape. Can be any shape except {@link + * net.rptools.maptool.model.ShapeType#GRID}. + * @param tokenFacingAngle The angle on-screen that the token is facing. Used for cones and beams + * to provide the main axis of the shape. + * @param visionRange The range to which the token can see. Determines the size of the shape. + * @param width For beams, the width of the beam. Otherwise, ignored. + * @param arcAngle For cones, the internal angle of the point of the cone. Otherwise, ignored. + * @param offsetAngle For cones and beams, an offset to apply relative to the token facing. + * Otherwise, ignored. + * @return The area of the light. + */ + protected @Nonnull Area getShapedAreaWithoutFootprint( ShapeType shape, - Token token, - double range, + int tokenFacingAngle, + double visionRange, double width, double arcAngle, - int offsetAngle, - boolean scaleWithToken) { - if (shape == null) { - shape = ShapeType.CIRCLE; - } - int visionDistance = zone.getTokenVisionInPixels(); - double visionRange = (range == 0) ? visionDistance : range * getSize() / zone.getUnitsPerCell(); - /* Token facing as an angle. 0° points to the right and clockwise is positive. */ - int tokenFacingAngle = token.getFacingInDegrees() + 90; - Rectangle footprint = token.getFootprint(this).getBounds(this); - - if (scaleWithToken) { - double footprintWidth = footprint.getWidth() / 2; - - // Test for gridless maps - var cellShape = getCellShape(); - if (cellShape == null) { - double tokenBoundsWidth = token.getBounds(getZone()).getWidth() / 2; - visionRange += (footprintWidth > tokenBoundsWidth) ? tokenBoundsWidth : tokenBoundsWidth; - } else { - // For grids, this will be the same, but for Hex's we'll use the smaller side depending on - // which Hex type you choose - double footprintHeight = footprint.getHeight() / 2; - visionRange += Math.min(footprintWidth, footprintHeight); - } - } - + int offsetAngle) { Area visibleArea; switch (shape) { case CIRCLE -> { @@ -438,9 +445,6 @@ public void setSize(int size) { GraphicsUtil.createLineSegmentEllipse( -visionRange, -visionRange, visionRange, visionRange, CIRCLE_SEGMENTS); } - case GRID -> { - visibleArea = getGridArea(token, range, scaleWithToken, visionRange); - } case SQUARE -> { visibleArea = new Area( @@ -472,15 +476,14 @@ public void setSize(int size) { GeneralPath path = new GeneralPath(); path.append(cone.getPathIterator(null, 1), false); visibleArea = new Area(path); - - var footprintPart = new Rectangle(footprint); - footprintPart.x = -footprintPart.width / 2; - footprintPart.y = -footprintPart.height / 2; - visibleArea.add(new Area(footprintPart)); } case HEX -> { visibleArea = createHex(visionRange); } + case GRID -> { + log.error("Shape {} should not be handled here. Returning empty area.", shape); + visibleArea = new Area(); + } default -> { log.error("Unhandled shape {}; treating as a circle", shape); visibleArea = @@ -492,6 +495,75 @@ public void setSize(int size) { return visibleArea; } + protected @Nonnull Area getFootprintShapedAreaForCone(Rectangle footprint) { + var footprintPart = new Rectangle(footprint); + footprintPart.x = -footprintPart.width / 2; + footprintPart.y = -footprintPart.height / 2; + return new Area(footprintPart); + } + + /** + * Called by SightType and Light class to return a vision area based upon a specified distance + * + * @param shape The shape of the light. Can be any {@link net.rptools.maptool.model.ShapeType} + * @param token Used to position the shape and to provide footprint + * @param range How far the shape should extends from the origin. If {@code 0}, the zone's vision + * range is used. + * @param arcAngle Only used by cone + * @param offsetAngle Arc distance from facing, only used by cone + * @param scaleWithToken used to increase the area based on token footprint + * @return Area + */ + public @Nonnull Area getShapedArea( + ShapeType shape, + Token token, + double range, + double width, + double arcAngle, + int offsetAngle, + boolean scaleWithToken) { + if (range == 0) { + range = zone.getTokenVisionDistance(); + } + double visionRange = range * getSize() / zone.getUnitsPerCell(); + + Rectangle footprint = token.getFootprint(this).getBounds(this); + + if (scaleWithToken) { + double footprintWidth = footprint.getWidth() / 2; + + // Test for gridless maps + var cellShape = getCellShape(); + if (cellShape == null) { + double tokenBoundsWidth = token.getBounds(zone).getWidth() / 2; + visionRange += (footprintWidth > tokenBoundsWidth) ? tokenBoundsWidth : tokenBoundsWidth; + } else { + // For grids, this will be the same, but for Hex's we'll use the smaller side depending on + // which Hex type you choose + double footprintHeight = footprint.getHeight() / 2; + visionRange += Math.min(footprintWidth, footprintHeight); + } + } + + // Grid shape is unique in that it is deliberately "unnatural". So handle it separately. + if (shape == ShapeType.GRID) { + return getGridArea(token, range, scaleWithToken, visionRange); + } + + var facingAngle = getTokenFacingAngleRelativeToGridAxis(token); + var visibleArea = + getShapedAreaWithoutFootprint( + shape, facingAngle, visionRange, width, arcAngle, offsetAngle); + if (shape == ShapeType.CONE) { + // Cones are unique in that they add the token footprint to the shape. + visibleArea.add(getFootprintShapedAreaForCone(footprint)); + } + + return visibleArea; + } + + // endregion + /** * Return the cell distance between two cells. Does not take into account terrain or VBL. * Overridden by Hex & Gridless grids. @@ -777,7 +849,7 @@ protected Area getGridArea( if (range > 0) { final Stopwatch stopwatch = Stopwatch.createStarted(); - final int gridRadius = (int) (range / getZone().getUnitsPerCell()); + final int gridRadius = (int) (range / zone.getUnitsPerCell()); if (scaleWithToken) { visibleArea = getScaledGridArea(token, gridRadius); diff --git a/src/main/java/net/rptools/maptool/model/IsometricGrid.java b/src/main/java/net/rptools/maptool/model/IsometricGrid.java index 5242e5e0c1..0e31b960fd 100644 --- a/src/main/java/net/rptools/maptool/model/IsometricGrid.java +++ b/src/main/java/net/rptools/maptool/model/IsometricGrid.java @@ -37,7 +37,6 @@ import net.rptools.maptool.client.walker.astar.AStarSquareEuclideanWalker; import net.rptools.maptool.server.proto.GridDto; import net.rptools.maptool.server.proto.IsometricGridDto; -import net.rptools.maptool.util.GraphicsUtil; public class IsometricGrid extends Grid { private static List footprintList; @@ -268,96 +267,40 @@ public void uninstallMovementKeys(Map actionMap) { } @Override - public @Nonnull Area getShapedArea( + protected int getTokenFacingAngleRelativeToGridAxis(Token token) { + return -(token.getFacing() + 45); + } + + @Override + protected @Nonnull Area getShapedAreaWithoutFootprint( ShapeType shape, - Token token, - double range, + int tokenFacingAngle, + double visionRange, double width, double arcAngle, - int offsetAngle, - boolean scaleWithToken) { - if (shape == null) { - shape = ShapeType.CIRCLE; - } - int visionDistance = getZone().getTokenVisionInPixels(); - double visionRange = - (range == 0) ? visionDistance : range * getSize() / getZone().getUnitsPerCell(); - int tokenFacingAngle = token.getFacingInDegrees() + 90; - Rectangle footprint = token.getFootprint(this).getBounds(this); - - if (scaleWithToken) { - visionRange += footprint.getHeight() / 2; - } + int offsetAngle) { + var orthoShape = + super.getShapedAreaWithoutFootprint( + shape, tokenFacingAngle, visionRange, width, arcAngle, offsetAngle); - Area visibleArea = new Area(); - switch (shape) { - case CIRCLE -> { - visionRange = (float) Math.sin(Math.toRadians(45)) * visionRange; - visibleArea = - GraphicsUtil.createLineSegmentEllipse( - -visionRange * 2, -visionRange, visionRange * 2, visionRange, CIRCLE_SEGMENTS); - } - case SQUARE -> { - int[] x = {0, (int) visionRange * 2, 0, (int) -visionRange * 2}; - int[] y = {(int) -visionRange, 0, (int) visionRange, 0}; - visibleArea = new Area(new Polygon(x, y, 4)); - } - case BEAM -> { - var pixelWidth = Math.max(2, width * getSize() / getZone().getUnitsPerCell()); - Shape visibleShape = new Rectangle2D.Double(0, -pixelWidth / 2, visionRange, pixelWidth); - - // new angle, corrected for isometric view - double theta = Math.toRadians(offsetAngle - tokenFacingAngle); - Point2D angleVector = new Point2D.Double(Math.cos(theta), Math.sin(theta)); - AffineTransform at = new AffineTransform(); - at.rotate(Math.PI / 4); - at.scale(1.0, 0.5); - at.deltaTransform(angleVector, angleVector); - - theta = -Math.atan2(angleVector.getY(), angleVector.getX()); - - visibleArea = - new Area( - AffineTransform.getRotateInstance(theta + Math.toRadians(45)) - .createTransformedShape(visibleShape)); - } - case CONE -> { - // Rotate the vision range by 45 degrees for isometric view - visionRange = (float) Math.sin(Math.toRadians(45)) * visionRange; - // Get the cone, use degreesFromIso to convert the facing from isometric to plan - - Arc2D cone = - new Arc2D.Double( - -visionRange * 2, - -visionRange, - visionRange * 4, - visionRange * 2, - (offsetAngle - tokenFacingAngle) - (arcAngle / 2.0), - arcAngle, - Arc2D.PIE); - GeneralPath path = new GeneralPath(); - path.append(cone.getPathIterator(null, 1), false); // Flatten the cone to remove 'curves' - Area tempvisibleArea = new Area(path); - - // convert the cell footprint to an area - Area cellShape = createCellShape(footprint.height); - // convert the area to isometric view - AffineTransform mtx = new AffineTransform(); - mtx.translate(-footprint.width / 2, -footprint.height / 2); - cellShape.transform(mtx); - // join cell footprint and cone to create viewable area - visibleArea.add(cellShape); - visibleArea.add(tempvisibleArea); - } - default -> { - log.error("Unhandled shape {}; treating as a circle", shape); - visionRange = (float) Math.sin(Math.toRadians(45)) * visionRange; - visibleArea = - GraphicsUtil.createLineSegmentEllipse( - -visionRange * 2, -visionRange, visionRange * 2, visionRange, CIRCLE_SEGMENTS); - } - } - return visibleArea; + AffineTransform at = new AffineTransform(); + var sqrt2 = Math.sqrt(2); + at.scale(sqrt2, sqrt2 / 2); + at.rotate(Math.PI / 4); + orthoShape.transform(at); + + return orthoShape; + } + + @Override + protected @Nonnull Area getFootprintShapedAreaForCone(Rectangle footprint) { + // convert the cell footprint to an area + Area cellShape = createCellShape(footprint.height); + // convert the area to isometric view + AffineTransform mtx = new AffineTransform(); + mtx.translate(-footprint.width / 2, -footprint.height / 2); + cellShape.transform(mtx); + return cellShape; } @Override diff --git a/src/main/java/net/rptools/maptool/model/Zone.java b/src/main/java/net/rptools/maptool/model/Zone.java index d5acf2dea0..3bd4ac6714 100644 --- a/src/main/java/net/rptools/maptool/model/Zone.java +++ b/src/main/java/net/rptools/maptool/model/Zone.java @@ -527,17 +527,6 @@ public void setTokenSelection(TokenSelection tokenSelection) { this.tokenSelection = tokenSelection; } - /** - * @return the distance in map pixels at a 1:1 zoom - */ - public int getTokenVisionInPixels() { - if (tokenVisionDistance == 0) { - // TODO: This is here to provide transition between pre 1.3b19 an 1.3b19. Remove later - tokenVisionDistance = DEFAULT_TOKEN_VISION_DISTANCE; - } - return Double.valueOf(tokenVisionDistance * grid.getSize() / getUnitsPerCell()).intValue(); - } - public void setFogPaint(DrawablePaint paint) { fogPaint = paint; } @@ -2101,6 +2090,11 @@ private void collapseDrawableLayer(List layer) { // Backward compatibility @SuppressWarnings("ConstantConditions") protected Object readResolve() { + if (tokenVisionDistance == 0) { + // 1.3b19 + tokenVisionDistance = DEFAULT_TOKEN_VISION_DISTANCE; + } + if ("".equals(playerAlias) || name.equals(playerAlias)) { // Don't keep redundant player aliases around. The display name will default to the name if // no player alias is set. From 7f962811861ecb3855d1948f7060111aef0b6e43 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Sun, 6 Oct 2024 16:24:49 -0700 Subject: [PATCH 076/146] Do not disclose GM maps to players in status UI This hides the name of the zone in the Connections window and the status bar it won't be revealed to players. --- .../client/swing/PlayersLoadingStatusBar.java | 24 ++++++++++++------- .../connections/PlayerListCellRenderer.java | 24 ++++++++++++------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java b/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java index a488df70d8..e184065a09 100644 --- a/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java +++ b/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java @@ -56,14 +56,22 @@ private void refreshCount() { new StringBuilder(I18N.getText("ConnectionStatusPanel.playersLoadedZone", loaded, total)); for (Player player : players) { - var zone = - player.getZoneId() == null ? null : MapTool.getCampaign().getZone(player.getZoneId()); - - var text = - I18N.getText( - player.getLoaded() ? "connections.playerIsInZone" : "connections.playerIsLoadingZone", - player.toString(), - zone == null ? null : zone.getDisplayName()); + String text; + if (player.isGM()) { + // Don't display zone information. + text = player.toString(); + } else { + var zone = + player.getZoneId() == null ? null : MapTool.getCampaign().getZone(player.getZoneId()); + text = + I18N.getText( + player.getLoaded() + ? "connections.playerIsInZone" + : "connections.playerIsLoadingZone", + player.toString(), + zone == null ? null : zone.getDisplayName()); + } + sb.append("\n"); sb.append(text); } diff --git a/src/main/java/net/rptools/maptool/client/ui/connections/PlayerListCellRenderer.java b/src/main/java/net/rptools/maptool/client/ui/connections/PlayerListCellRenderer.java index 65ff110126..3ad5382049 100644 --- a/src/main/java/net/rptools/maptool/client/ui/connections/PlayerListCellRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/connections/PlayerListCellRenderer.java @@ -26,16 +26,24 @@ public class PlayerListCellRenderer extends DefaultListCellRenderer { public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean hasFocus) { if (value instanceof Player player) { - var zone = - player.getZoneId() == null ? null : MapTool.getCampaign().getZone(player.getZoneId()); - - String text = - I18N.getText( - player.getLoaded() ? "connections.playerIsInZone" : "connections.playerIsLoadingZone", - player.toString(), - zone == null ? null : zone.getDisplayName()); + String text; + if (player.isGM()) { + // Don't display zone information. + text = player.toString(); + } else { + var zone = + player.getZoneId() == null ? null : MapTool.getCampaign().getZone(player.getZoneId()); + text = + I18N.getText( + player.getLoaded() + ? "connections.playerIsInZone" + : "connections.playerIsLoadingZone", + player.toString(), + zone == null ? null : zone.getDisplayName()); + } return super.getListCellRendererComponent(list, text, index, isSelected, hasFocus); } + return super.getListCellRendererComponent(list, value, index, isSelected, hasFocus); } } From 84b5bd239cd533abee385e1101cf2ab0270edbe4 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 10 Oct 2024 19:17:39 -0700 Subject: [PATCH 077/146] Allow GMs to see each other's zone again --- .../maptool/client/swing/PlayersLoadingStatusBar.java | 10 ++++++---- .../client/ui/connections/PlayerListCellRenderer.java | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java b/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java index e184065a09..7ff44b0fe1 100644 --- a/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java +++ b/src/main/java/net/rptools/maptool/client/swing/PlayersLoadingStatusBar.java @@ -56,11 +56,11 @@ private void refreshCount() { new StringBuilder(I18N.getText("ConnectionStatusPanel.playersLoadedZone", loaded, total)); for (Player player : players) { + // GMs can see everyone's zone, players can only see each other's. + var showZone = MapTool.getPlayer().isGM() || !player.isGM(); + String text; - if (player.isGM()) { - // Don't display zone information. - text = player.toString(); - } else { + if (showZone) { var zone = player.getZoneId() == null ? null : MapTool.getCampaign().getZone(player.getZoneId()); text = @@ -70,6 +70,8 @@ private void refreshCount() { : "connections.playerIsLoadingZone", player.toString(), zone == null ? null : zone.getDisplayName()); + } else { + text = player.toString(); } sb.append("\n"); diff --git a/src/main/java/net/rptools/maptool/client/ui/connections/PlayerListCellRenderer.java b/src/main/java/net/rptools/maptool/client/ui/connections/PlayerListCellRenderer.java index 3ad5382049..fce6eb02b0 100644 --- a/src/main/java/net/rptools/maptool/client/ui/connections/PlayerListCellRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/connections/PlayerListCellRenderer.java @@ -26,11 +26,11 @@ public class PlayerListCellRenderer extends DefaultListCellRenderer { public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean hasFocus) { if (value instanceof Player player) { + // GMs can see everyone's zone, players can only see each other's. + var showZone = MapTool.getPlayer().isGM() || !player.isGM(); + String text; - if (player.isGM()) { - // Don't display zone information. - text = player.toString(); - } else { + if (showZone) { var zone = player.getZoneId() == null ? null : MapTool.getCampaign().getZone(player.getZoneId()); text = @@ -40,6 +40,8 @@ public Component getListCellRendererComponent( : "connections.playerIsLoadingZone", player.toString(), zone == null ? null : zone.getDisplayName()); + } else { + text = player.toString(); } return super.getListCellRendererComponent(list, text, index, isSelected, hasFocus); } From 4f3cab50fd1fbd05150bbc2e621748ddd4947d24 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 10 Oct 2024 21:34:46 -0700 Subject: [PATCH 078/146] Remove unused null parameter causing varargs warning on compile --- .../client/ui/campaignproperties/CampaignPropertiesDialog.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java index cc1a5d4ec6..4f6aa5dd51 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java @@ -451,8 +451,7 @@ private void initExportButton() { MapTool.showMessage( "CampaignPropertiesDialog.export.message", "msg.title.exportProperties", - JOptionPane.INFORMATION_MESSAGE, - null); + JOptionPane.INFORMATION_MESSAGE); CampaignPropertiesDto campaignPropertiesDto = MapTool.getCampaign().getCampaignProperties().toDto(); FileOutputStream fos = new FileOutputStream(chooser.getSelectedFile()); From 3f9792b217ce6f28e2ee55b093c07596ae153cbe Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Fri, 11 Oct 2024 20:16:06 +0800 Subject: [PATCH 079/146] New translations i18n.properties (German) --- .../net/rptools/maptool/language/i18n_de_DE.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties b/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties index 892f52d239..c46e199bdf 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties @@ -810,7 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = Vordefinitionen importieren CampaignPropertiesDialog.button.importPredefined.tooltip = Vordefinierte Eigenschaften und Einstellungen für das ausgewählte System importieren CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = Kampagneneigenschaften in Datei exportieren -CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. +CampaignPropertiesDialog.export.message = Kampagneneigenschaften werden im JSON-Format exportiert und können nicht importiert werden. CampaignPropertiesDialog.combo.importPredefined.tooltip = System für das Eigenschaften importiert werden CampaignPropertiesDialog.button.import = Vordefinitionen importieren campaignPropertiesDialog.newTokenTypeName = Name des neuen Spielmarkentyps From 1adb3e79239fd9d31a9bcfad65039b3224872325 Mon Sep 17 00:00:00 2001 From: Jmr3366 <100969108+Jmr3366@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:06:25 -0400 Subject: [PATCH 080/146] Update TokenImage.java Added request changes to use constants Changed empty variable check Added NPE check for bad bytes --- .../maptool/client/functions/TokenImage.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java index d68cea53c5..5e746f1ad3 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java @@ -57,7 +57,10 @@ final int getValue() { public static final String SET_IMAGE = "setImage"; public static final String SET_PORTRAIT = "setTokenPortrait"; - public static final String SET_HANDOUT = "setTokenHandout"; + public static final String SET_HANDOUT = "setTokenHandout"; + public static final String FILE_HEADER_WEBP = "RIFF"; + public static final String FILE_HEADER_JPG = "ÿØÿà"; + public static final String FILE_HEADER_PNG = "‰PNG"; private TokenImage() { super( @@ -178,15 +181,18 @@ public Object childEvaluate( FunctionUtil.checkNumberParam(functionName, args, 2, 2); String imageName = args.get(0).toString(); String imageString = args.get(1).toString(); - if (imageName == "" || imageString == "") { - throw new ParserException( - I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); - } else { - if (imageString.length() > 8) { + if (imageName.isEmpty() || imageString.isEmpty()) { + throw new ParserException( + I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); + } else if(imageString.length() > 8) { byte[] imageBytes = Base64.decode(imageString); - String imageCheck = new String(imageBytes, 0, 4); - /* header check for: webp || jpg || png */ - if (imageCheck.equals("RIFF") || imageCheck.equals("ÿØÿà") || imageCheck.equals("‰PNG")) { + String imageCheck; + try { + imageCheck = new String(imageBytes, 0, 4); + } catch (Exception e) { + throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); + } + if (imageCheck.equals(FILE_HEADER_WEBP) || imageCheck.equals(FILE_HEADER_JPG) || imageCheck.equals(FILE_HEADER_PNG)) { Asset asset = Asset.createImageAsset(imageName, imageBytes); AssetManager.putAsset(asset); assetId.append(asset.getMD5Key().toString()); @@ -198,7 +204,6 @@ public Object childEvaluate( throw new ParserException( I18N.getText("macro.function.general.wrongParamType", functionName)); } - } } /* getImage, getTokenImage, getTokenPortrait, or getTokenHandout */ From 6e1c9f3096f5d80ea8c215602ce32c2192d8eebf Mon Sep 17 00:00:00 2001 From: Jmr3366 <100969108+Jmr3366@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:09:24 -0400 Subject: [PATCH 081/146] Update TokenImage.java spotless proper --- .../maptool/client/functions/TokenImage.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java index 5e746f1ad3..33c80ee2a7 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java @@ -182,28 +182,28 @@ public Object childEvaluate( String imageName = args.get(0).toString(); String imageString = args.get(1).toString(); if (imageName.isEmpty() || imageString.isEmpty()) { - throw new ParserException( - I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); + throw new ParserException( + I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); } else if(imageString.length() > 8) { - byte[] imageBytes = Base64.decode(imageString); - String imageCheck; - try { - imageCheck = new String(imageBytes, 0, 4); - } catch (Exception e) { - throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); - } - if (imageCheck.equals(FILE_HEADER_WEBP) || imageCheck.equals(FILE_HEADER_JPG) || imageCheck.equals(FILE_HEADER_PNG)) { - Asset asset = Asset.createImageAsset(imageName, imageBytes); - AssetManager.putAsset(asset); - assetId.append(asset.getMD5Key().toString()); - return assetId; - } else { - throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); - } + byte[] imageBytes = Base64.decode(imageString); + String imageCheck; + try { + imageCheck = new String(imageBytes, 0, 4); + } catch (Exception e) { + throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); + } + if (imageCheck.equals(FILE_HEADER_WEBP) || imageCheck.equals(FILE_HEADER_JPG) || imageCheck.equals(FILE_HEADER_PNG)) { + Asset asset = Asset.createImageAsset(imageName, imageBytes); + AssetManager.putAsset(asset); + assetId.append(asset.getMD5Key().toString()); + return assetId; } else { - throw new ParserException( - I18N.getText("macro.function.general.wrongParamType", functionName)); + throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); } + } else { + throw new ParserException( + I18N.getText("macro.function.general.wrongParamType", functionName)); + } } /* getImage, getTokenImage, getTokenPortrait, or getTokenHandout */ From 96d58b5c822d2d01bd4ef6d8c37514f31c58ed4c Mon Sep 17 00:00:00 2001 From: jmr3366 Date: Fri, 11 Oct 2024 10:16:34 -0400 Subject: [PATCH 082/146] Resolves #483 --- .../maptool/client/functions/TokenImage.java | 97 ++++++++++--------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java index d68cea53c5..e4e5b08991 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java @@ -58,22 +58,25 @@ final int getValue() { public static final String SET_IMAGE = "setImage"; public static final String SET_PORTRAIT = "setTokenPortrait"; public static final String SET_HANDOUT = "setTokenHandout"; + public static final String FILE_HEADER_WEBP = "RIFF"; + public static final String FILE_HEADER_JPG = "ÿØÿà"; + public static final String FILE_HEADER_PNG = "‰PNG"; private TokenImage() { super( - 0, - 3, - "getTokenImage", - "getTokenPortrait", - "getTokenHandout", - "setTokenImage", - "setTokenPortrait", - "setTokenHandout", - "getImage", - "setTokenOpacity", - "getAssetProperties", - "getTokenOpacity", - "createAsset"); + 0, + 3, + "getTokenImage", + "getTokenPortrait", + "getTokenHandout", + "setTokenImage", + "setTokenPortrait", + "setTokenHandout", + "getImage", + "setTokenOpacity", + "getAssetProperties", + "getTokenOpacity", + "createAsset"); } /** @@ -87,8 +90,8 @@ public static TokenImage getInstance() { @Override public Object childEvaluate( - Parser parser, VariableResolver resolver, String functionName, List args) - throws ParserException { + Parser parser, VariableResolver resolver, String functionName, List args) + throws ParserException { Token token; if (functionName.equalsIgnoreCase("setTokenOpacity")) { @@ -158,8 +161,8 @@ public Object childEvaluate( properties.addProperty("name", asset.getName()); Image img = - ImageManager.getImageAndWait( - asset.getMD5Key()); // wait until loaded, so width/height are correct + ImageManager.getImageAndWait( + asset.getMD5Key()); // wait until loaded, so width/height are correct String status = "loaded"; if (img == ImageManager.BROKEN_IMAGE) { status = "broken"; @@ -178,26 +181,28 @@ public Object childEvaluate( FunctionUtil.checkNumberParam(functionName, args, 2, 2); String imageName = args.get(0).toString(); String imageString = args.get(1).toString(); - if (imageName == "" || imageString == "") { + if (imageName.isEmpty() || imageString.isEmpty()) { throw new ParserException( - I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); - } else { - if (imageString.length() > 8) { - byte[] imageBytes = Base64.decode(imageString); - String imageCheck = new String(imageBytes, 0, 4); - /* header check for: webp || jpg || png */ - if (imageCheck.equals("RIFF") || imageCheck.equals("ÿØÿà") || imageCheck.equals("‰PNG")) { - Asset asset = Asset.createImageAsset(imageName, imageBytes); - AssetManager.putAsset(asset); - assetId.append(asset.getMD5Key().toString()); - return assetId; - } else { - throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); - } + I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); + } else if(imageString.length() > 8) { + byte[] imageBytes = Base64.decode(imageString); + String imageCheck; + try { + imageCheck = new String(imageBytes, 0, 4); + } catch (Exception e) { + throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); + } + if (imageCheck.equals(FILE_HEADER_WEBP) || imageCheck.equals(FILE_HEADER_JPG) || imageCheck.equals(FILE_HEADER_PNG)) { + Asset asset = Asset.createImageAsset(imageName, imageBytes); + AssetManager.putAsset(asset); + assetId.append(asset.getMD5Key().toString()); + return assetId; } else { - throw new ParserException( - I18N.getText("macro.function.general.wrongParamType", functionName)); + throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); } + } else { + throw new ParserException( + I18N.getText("macro.function.general.wrongParamType", functionName)); } } @@ -241,18 +246,18 @@ public Object childEvaluate( } assetId.append(token.getImageAssetId().toString()); } else if ("getTokenHandout" - .equalsIgnoreCase(functionName)) { // getTokenHandout, or different capitalization + .equalsIgnoreCase(functionName)) { // getTokenHandout, or different capitalization if (token.getCharsheetImage() == null) { return ""; } assetId.append(token.getCharsheetImage().toString()); } else { throw new ParserException( - I18N.getText("macro.function.general.unknownFunction", functionName)); + I18N.getText("macro.function.general.unknownFunction", functionName)); } if (indexSize >= 0 - && !"".equals(args.get(indexSize).toString())) { // if size parameter entered and not "" + && !"".equals(args.get(indexSize).toString())) { // if size parameter entered and not "" if (args.get(indexSize) instanceof BigDecimal) { assetId.append("-"); BigDecimal size = (BigDecimal) args.get(indexSize); @@ -261,11 +266,11 @@ public Object childEvaluate( assetId.append(i); } else { throw new ParserException( - I18N.getText( - "macro.function.general.argumentTypeInvalid", - functionName, - indexSize + 1, - args.get(indexSize).toString())); + I18N.getText( + "macro.function.general.argumentTypeInvalid", + functionName, + indexSize + 1, + args.get(indexSize).toString())); } } @@ -291,7 +296,7 @@ public static MD5Key getMD5Key(String assetName, String functionName) throws Par Token imageToken = findImageToken(assetName, functionName); if (imageToken == null) { throw new ParserException( - I18N.getText("macro.function.general.unknownToken", functionName, assetName)); + I18N.getText("macro.function.general.unknownToken", functionName, assetName)); } assetId = imageToken.getImageAssetId().toString(); } else { @@ -302,7 +307,7 @@ public static MD5Key getMD5Key(String assetName, String functionName) throws Par } if (assetId == null) { throw new ParserException( - I18N.getText("macro.function.general.argumentTypeInvalid", functionName, 1, assetName)); + I18N.getText("macro.function.general.argumentTypeInvalid", functionName, 1, assetName)); } else { return new MD5Key(assetId); } @@ -311,7 +316,7 @@ public static MD5Key getMD5Key(String assetName, String functionName) throws Par private static void setImage(Token token, String assetName) throws ParserException { MD5Key md5key = getMD5Key(assetName, SET_IMAGE); MapTool.serverCommand() - .updateTokenProperty(token, Token.Update.setImageAsset, (String) null, md5key.toString()); + .updateTokenProperty(token, Token.Update.setImageAsset, (String) null, md5key.toString()); } private static void setPortrait(Token token, String assetName) throws ParserException { @@ -330,7 +335,7 @@ private static Token findImageToken(final String name, String functionName) { List zrenderers = MapTool.getFrame().getZoneRenderers(); for (ZoneRenderer zr : zrenderers) { List tokenList = - zr.getZone().getTokensFiltered(t -> t.getName().equalsIgnoreCase(name)); + zr.getZone().getTokensFiltered(t -> t.getName().equalsIgnoreCase(name)); for (Token token : tokenList) { // If we are not the GM and the token is not visible to players then we don't // let them get functions from it. From 557badedf8c0d65712205f51ace2d87c46436fe7 Mon Sep 17 00:00:00 2001 From: Jmr3366 <100969108+Jmr3366@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:42:29 -0400 Subject: [PATCH 083/146] Update TokenImage.java spotless I hate thee --- .../maptool/client/functions/TokenImage.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java index 33c80ee2a7..5c7d2d3e43 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java @@ -57,7 +57,7 @@ final int getValue() { public static final String SET_IMAGE = "setImage"; public static final String SET_PORTRAIT = "setTokenPortrait"; - public static final String SET_HANDOUT = "setTokenHandout"; + public static final String SET_HANDOUT = "setTokenHandout"; public static final String FILE_HEADER_WEBP = "RIFF"; public static final String FILE_HEADER_JPG = "ÿØÿà"; public static final String FILE_HEADER_PNG = "‰PNG"; @@ -181,10 +181,10 @@ public Object childEvaluate( FunctionUtil.checkNumberParam(functionName, args, 2, 2); String imageName = args.get(0).toString(); String imageString = args.get(1).toString(); - if (imageName.isEmpty() || imageString.isEmpty()) { + if (imageName.isEmpty() || imageString.isEmpty()) { throw new ParserException( - I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); - } else if(imageString.length() > 8) { + I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); + } else if (imageString.length() > 8) { byte[] imageBytes = Base64.decode(imageString); String imageCheck; try { @@ -192,7 +192,9 @@ public Object childEvaluate( } catch (Exception e) { throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); } - if (imageCheck.equals(FILE_HEADER_WEBP) || imageCheck.equals(FILE_HEADER_JPG) || imageCheck.equals(FILE_HEADER_PNG)) { + if (imageCheck.equals(FILE_HEADER_WEBP) + || imageCheck.equals(FILE_HEADER_JPG) + || imageCheck.equals(FILE_HEADER_PNG)) { Asset asset = Asset.createImageAsset(imageName, imageBytes); AssetManager.putAsset(asset); assetId.append(asset.getMD5Key().toString()); @@ -202,7 +204,7 @@ public Object childEvaluate( } } else { throw new ParserException( - I18N.getText("macro.function.general.wrongParamType", functionName)); + I18N.getText("macro.function.general.wrongParamType", functionName)); } } From dd44a050d7cae36db3ca73f15f0c9fd010e7d06d Mon Sep 17 00:00:00 2001 From: jmr3366 Date: Fri, 11 Oct 2024 11:11:35 -0400 Subject: [PATCH 084/146] Resolves #483 --- .../rptools/maptool/client/functions/TokenImage.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java index 1ae2ed2386..99368f4e12 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java @@ -181,9 +181,9 @@ public Object childEvaluate( FunctionUtil.checkNumberParam(functionName, args, 2, 2); String imageName = args.get(0).toString(); String imageString = args.get(1).toString(); - if (imageName.isEmpty() || imageString.isEmpty()) { + if (imageName.isEmpty() || imageString.isEmpty()) { throw new ParserException( - I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); + I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); } else if (imageString.length() > 8) { byte[] imageBytes = Base64.decode(imageString); String imageCheck; @@ -193,8 +193,8 @@ public Object childEvaluate( throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); } if (imageCheck.equals(FILE_HEADER_WEBP) - || imageCheck.equals(FILE_HEADER_JPG) - || imageCheck.equals(FILE_HEADER_PNG)) { + || imageCheck.equals(FILE_HEADER_JPG) + || imageCheck.equals(FILE_HEADER_PNG)) { Asset asset = Asset.createImageAsset(imageName, imageBytes); AssetManager.putAsset(asset); assetId.append(asset.getMD5Key().toString()); @@ -204,7 +204,7 @@ public Object childEvaluate( } } else { throw new ParserException( - I18N.getText("macro.function.general.wrongParamType", functionName)); + I18N.getText("macro.function.general.wrongParamType", functionName)); } } @@ -363,4 +363,4 @@ private static Token findImageToken(final String name, String functionName) { // throw new ParserException(I18N.getText("macro.function.general.unknownToken", functionName, // name)); } -} +} \ No newline at end of file From 62224096e2e3bad96fccc5691d8a07d3248a35ec Mon Sep 17 00:00:00 2001 From: jmr3366 Date: Fri, 11 Oct 2024 11:18:20 -0400 Subject: [PATCH 085/146] Resolves #483 --- .../maptool/client/functions/TokenImage.java | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java index 99368f4e12..854827c0ea 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java @@ -64,19 +64,19 @@ final int getValue() { private TokenImage() { super( - 0, - 3, - "getTokenImage", - "getTokenPortrait", - "getTokenHandout", - "setTokenImage", - "setTokenPortrait", - "setTokenHandout", - "getImage", - "setTokenOpacity", - "getAssetProperties", - "getTokenOpacity", - "createAsset"); + 0, + 3, + "getTokenImage", + "getTokenPortrait", + "getTokenHandout", + "setTokenImage", + "setTokenPortrait", + "setTokenHandout", + "getImage", + "setTokenOpacity", + "getAssetProperties", + "getTokenOpacity", + "createAsset"); } /** @@ -90,8 +90,8 @@ public static TokenImage getInstance() { @Override public Object childEvaluate( - Parser parser, VariableResolver resolver, String functionName, List args) - throws ParserException { + Parser parser, VariableResolver resolver, String functionName, List args) + throws ParserException { Token token; if (functionName.equalsIgnoreCase("setTokenOpacity")) { @@ -161,8 +161,8 @@ public Object childEvaluate( properties.addProperty("name", asset.getName()); Image img = - ImageManager.getImageAndWait( - asset.getMD5Key()); // wait until loaded, so width/height are correct + ImageManager.getImageAndWait( + asset.getMD5Key()); // wait until loaded, so width/height are correct String status = "loaded"; if (img == ImageManager.BROKEN_IMAGE) { status = "broken"; @@ -183,7 +183,7 @@ public Object childEvaluate( String imageString = args.get(1).toString(); if (imageName.isEmpty() || imageString.isEmpty()) { throw new ParserException( - I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); + I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); } else if (imageString.length() > 8) { byte[] imageBytes = Base64.decode(imageString); String imageCheck; @@ -193,8 +193,8 @@ public Object childEvaluate( throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); } if (imageCheck.equals(FILE_HEADER_WEBP) - || imageCheck.equals(FILE_HEADER_JPG) - || imageCheck.equals(FILE_HEADER_PNG)) { + || imageCheck.equals(FILE_HEADER_JPG) + || imageCheck.equals(FILE_HEADER_PNG)) { Asset asset = Asset.createImageAsset(imageName, imageBytes); AssetManager.putAsset(asset); assetId.append(asset.getMD5Key().toString()); @@ -204,7 +204,7 @@ public Object childEvaluate( } } else { throw new ParserException( - I18N.getText("macro.function.general.wrongParamType", functionName)); + I18N.getText("macro.function.general.wrongParamType", functionName)); } } @@ -248,18 +248,18 @@ public Object childEvaluate( } assetId.append(token.getImageAssetId().toString()); } else if ("getTokenHandout" - .equalsIgnoreCase(functionName)) { // getTokenHandout, or different capitalization + .equalsIgnoreCase(functionName)) { // getTokenHandout, or different capitalization if (token.getCharsheetImage() == null) { return ""; } assetId.append(token.getCharsheetImage().toString()); } else { throw new ParserException( - I18N.getText("macro.function.general.unknownFunction", functionName)); + I18N.getText("macro.function.general.unknownFunction", functionName)); } if (indexSize >= 0 - && !"".equals(args.get(indexSize).toString())) { // if size parameter entered and not "" + && !"".equals(args.get(indexSize).toString())) { // if size parameter entered and not "" if (args.get(indexSize) instanceof BigDecimal) { assetId.append("-"); BigDecimal size = (BigDecimal) args.get(indexSize); @@ -268,11 +268,11 @@ public Object childEvaluate( assetId.append(i); } else { throw new ParserException( - I18N.getText( - "macro.function.general.argumentTypeInvalid", - functionName, - indexSize + 1, - args.get(indexSize).toString())); + I18N.getText( + "macro.function.general.argumentTypeInvalid", + functionName, + indexSize + 1, + args.get(indexSize).toString())); } } @@ -298,7 +298,7 @@ public static MD5Key getMD5Key(String assetName, String functionName) throws Par Token imageToken = findImageToken(assetName, functionName); if (imageToken == null) { throw new ParserException( - I18N.getText("macro.function.general.unknownToken", functionName, assetName)); + I18N.getText("macro.function.general.unknownToken", functionName, assetName)); } assetId = imageToken.getImageAssetId().toString(); } else { @@ -309,7 +309,7 @@ public static MD5Key getMD5Key(String assetName, String functionName) throws Par } if (assetId == null) { throw new ParserException( - I18N.getText("macro.function.general.argumentTypeInvalid", functionName, 1, assetName)); + I18N.getText("macro.function.general.argumentTypeInvalid", functionName, 1, assetName)); } else { return new MD5Key(assetId); } @@ -318,7 +318,7 @@ public static MD5Key getMD5Key(String assetName, String functionName) throws Par private static void setImage(Token token, String assetName) throws ParserException { MD5Key md5key = getMD5Key(assetName, SET_IMAGE); MapTool.serverCommand() - .updateTokenProperty(token, Token.Update.setImageAsset, (String) null, md5key.toString()); + .updateTokenProperty(token, Token.Update.setImageAsset, (String) null, md5key.toString()); } private static void setPortrait(Token token, String assetName) throws ParserException { @@ -337,7 +337,7 @@ private static Token findImageToken(final String name, String functionName) { List zrenderers = MapTool.getFrame().getZoneRenderers(); for (ZoneRenderer zr : zrenderers) { List tokenList = - zr.getZone().getTokensFiltered(t -> t.getName().equalsIgnoreCase(name)); + zr.getZone().getTokensFiltered(t -> t.getName().equalsIgnoreCase(name)); for (Token token : tokenList) { // If we are not the GM and the token is not visible to players then we don't // let them get functions from it. @@ -363,4 +363,4 @@ private static Token findImageToken(final String name, String functionName) { // throw new ParserException(I18N.getText("macro.function.general.unknownToken", functionName, // name)); } -} \ No newline at end of file +} From 14450408192773a3cf9d6674e22d57e1cf831956 Mon Sep 17 00:00:00 2001 From: jmr3366 Date: Fri, 11 Oct 2024 15:29:37 -0400 Subject: [PATCH 086/146] Resolves #483 added https url as an option --- .../maptool/client/functions/TokenImage.java | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java index 854827c0ea..f9fe2186d2 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java @@ -17,8 +17,15 @@ import com.google.gson.JsonObject; import com.jidesoft.utils.Base64; import java.awt.Image; +import java.awt.image.BufferedImage; +import java.io.IOException; import java.math.BigDecimal; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.util.List; +import javax.imageio.ImageIO; import net.rptools.lib.MD5Key; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; @@ -185,23 +192,41 @@ public Object childEvaluate( throw new ParserException( I18N.getText("macro.function.general.paramCannotBeEmpty", functionName)); } else if (imageString.length() > 8) { - byte[] imageBytes = Base64.decode(imageString); - String imageCheck; - try { - imageCheck = new String(imageBytes, 0, 4); - } catch (Exception e) { - throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); - } - if (imageCheck.equals(FILE_HEADER_WEBP) - || imageCheck.equals(FILE_HEADER_JPG) - || imageCheck.equals(FILE_HEADER_PNG)) { - Asset asset = Asset.createImageAsset(imageName, imageBytes); - AssetManager.putAsset(asset); - assetId.append(asset.getMD5Key().toString()); - return assetId; + Asset asset; + if (imageString.toLowerCase().startsWith("https://") + && (imageString.toLowerCase().endsWith(".jpg") + || imageString.toLowerCase().endsWith(".png") + || imageString.toLowerCase().endsWith(".webp"))) { + try { + URI uri = new URI(imageString); + URL url = uri.toURL(); + BufferedImage imageRAW = ImageIO.read(url); + asset = Asset.createImageAsset(imageName, imageRAW); + } catch (URISyntaxException | MalformedURLException | IllegalArgumentException e) { + throw new ParserException( + I18N.getText("macro.function.input.illegalArgumentType", imageString)); + } catch (IOException e1) { + throw new ParserException(I18N.getText("macro.function.html5.invalidURI", imageString)); + } } else { - throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); + byte[] imageBytes = Base64.decode(imageString); + String imageCheck; + try { + imageCheck = new String(imageBytes, 0, 4); + } catch (Exception e) { + throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); + } + if (imageCheck.equals(FILE_HEADER_WEBP) + || imageCheck.equals(FILE_HEADER_JPG) + || imageCheck.equals(FILE_HEADER_PNG)) { + asset = Asset.createImageAsset(imageName, imageBytes); + } else { + throw new ParserException(I18N.getText("dragdrop.unsupportedType", functionName)); + } } + AssetManager.putAsset(asset); + assetId.append(asset.getMD5Key().toString()); + return assetId; } else { throw new ParserException( I18N.getText("macro.function.general.wrongParamType", functionName)); From 1fccad6e5da54b4f9d8b8735d68e03264b115c02 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Sun, 13 Oct 2024 12:49:25 +0800 Subject: [PATCH 087/146] New translations i18n.properties (Japanese) --- .../net/rptools/maptool/language/i18n_ja_JP.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties b/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties index 40c5d8c3bb..c144ddd0ef 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties @@ -810,7 +810,7 @@ CampaignPropertiesDialog.button.importPredefined = 定義済み設定を取り CampaignPropertiesDialog.button.importPredefined.tooltip = 選択したシステム向けに予め定義された特性値と設定を取り込む CampaignPropertiesDialog.button.import.tooltip = Import predefined properties and settings for the selected system CampaignPropertiesDialog.button.export.tooltip = キャンペーン設定をファイルに書き出す -CampaignPropertiesDialog.export.message = Campaign properties will be exported in JSON format and cannot be imported. +CampaignPropertiesDialog.export.message = キャンペーン設定はJSON形式で書き出されます。読み込む事はできません。 CampaignPropertiesDialog.combo.importPredefined.tooltip = 取り込む特性値のシステム名称 CampaignPropertiesDialog.button.import = 定義済み設定を取り込む campaignPropertiesDialog.newTokenTypeName = 新しいトークン種別の名称を入力する From 06cacc2478a54d9e6c69816c5aa45650c425b6be Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:58:29 +0800 Subject: [PATCH 088/146] Update source file i18n.properties --- .../rptools/maptool/language/i18n.properties | 42 +------------------ 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 09fb8740fb..e28a6e524e 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2013,8 +2013,6 @@ macro.function.general.unknownProperty = Error executing "{0}": the macro.function.general.unknownTokenOnMap = Error executing "{0}": the token name or id "{1}" is unknown on map "{2}". macro.function.general.wrongNumParam = Function "{0}" requires exactly {1} parameters; {2} were provided. macro.function.general.listCannotBeEmpty = {0}: string list at argument {1} cannot be empty -macro.function.general.wrongParamType = A parameter is the wrong type. -macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Invalid metric type "{0}". @@ -2931,8 +2929,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can''t be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2974,7 +2970,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2985,41 +2980,8 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing \ purposes only.
Within your add-on use
lib://net.rptools.maptool/css/mt-stat-sheet.css \ or
lib://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = ### Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can''t convert {0} to {1}. From db9a8b28dbf4cd4742e06967a82a37a011c8412c Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:12:58 +0800 Subject: [PATCH 089/146] New translations i18n.properties (Danish) --- .../maptool/language/i18n_da_DK.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_da_DK.properties b/src/main/resources/net/rptools/maptool/language/i18n_da_DK.properties index 52081602a2..1e95da2ea1 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_da_DK.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_da_DK.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Error executing "{0}"\: the token name or id "{1}" is unknown on map "{2}". macro.function.general.wrongNumParam = Function "{0}" requires exactly {1} parameters; {2} were provided. macro.function.general.listCannotBeEmpty = {0}\: string list at argument {1} cannot be empty +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Ugyldig metrisk type "{0}". @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Importer udvidelsesbibliotek. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Beskrivelse library.dialog.addon.readMeFile = Vis "Læs mig" fil library.dialog.addon.authors = Forfattere library.dialog.addon.license = Licens +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = Vis licensfil library.dialog.addon.giturl = Git URL library.dialog.addon.website = Hjemmeside @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Kan ikke konvertere {0} til {1}. From db9a7b10bccf007024114ae9ea52ac9c888b0153 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:12:59 +0800 Subject: [PATCH 090/146] New translations i18n.properties (French) --- .../maptool/language/i18n_fr_FR.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_fr_FR.properties b/src/main/resources/net/rptools/maptool/language/i18n_fr_FR.properties index 97e09ff43b..791a2d53f1 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_fr_FR.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_fr_FR.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Erreur en éxécutant {0}\: Le nom ou l'id du pion '{1}' est inconnu dans la carte '{2}'. macro.function.general.wrongNumParam = La fonction {0} requiert exactement {1} paramètre(s); {2} ont été fournis. macro.function.general.listCannotBeEmpty = {0}\: la liste {1} ne peut pas être vide +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Type Métrique {0} invalide. @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From cc6c0a8a5727b0ba0b46fd5826f4394489f2c675 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:01 +0800 Subject: [PATCH 091/146] New translations i18n.properties (Spanish) --- .../maptool/language/i18n_es_ES.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_es_ES.properties b/src/main/resources/net/rptools/maptool/language/i18n_es_ES.properties index c2ecc90c4e..241fe34781 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_es_ES.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_es_ES.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Error al ejecutar "{0}"\: el nombre o id de ficha "{1}" es desconocido en el mapa "{2}". macro.function.general.wrongNumParam = La función "{0}" requiere exactamente {1} parámetros; {2} fueron proporcionados. macro.function.general.listCannotBeEmpty = {0}\: string list at argument {1} cannot be empty +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Tipo de medida "{0}" inválida. @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From f9baaaec468bbf33c4d22967f4873520babb4b91 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:02 +0800 Subject: [PATCH 092/146] New translations i18n.properties (Czech) --- .../maptool/language/i18n_cs_CZ.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_cs_CZ.properties b/src/main/resources/net/rptools/maptool/language/i18n_cs_CZ.properties index 2c86e69cec..c80b2819ef 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_cs_CZ.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_cs_CZ.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Error executing "{0}"\: the token name or id "{1}" is unknown on map "{2}". macro.function.general.wrongNumParam = Function "{0}" requires exactly {1} parameters; {2} were provided. macro.function.general.listCannotBeEmpty = {0}\: string list at argument {1} cannot be empty +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Invalid metric type "{0}". @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 0488b2905a5dfc3871d67111bc904bba216bb4b2 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:04 +0800 Subject: [PATCH 093/146] New translations i18n.properties (German) --- .../maptool/language/i18n_de_DE.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties b/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties index c46e199bdf..fb1032259b 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Fehler beim Ausführen von macro.function.general.unknownTokenOnMap = Fehler beim Ausführen von "{0}"\: der Spielmarkenname oder die Id "{1}" ist auf der Karte "{2}" unbekannt. macro.function.general.wrongNumParam = Funktion "{0}" benötigt exakt {1} Parameter; {2} wurden angegeben. macro.function.general.listCannotBeEmpty = {0}\: String-Liste bei Argument {1} darf nicht leer sein +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Ungültiger Metriktyp "{0}". @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Ungültige Bibliotheksdefinition. library.error.emptyName = Der Name einer Zusatzbibliothek darf nicht leer sein. library.error.emptyVersion = Die Version der Zusatzbibliothek {0} darf nicht leer sein. library.dialog.import.title = Importiere Zusatzbibliothek. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error beim Drop-In-Bibliotheksimport. library.import.error = Fehler beim Drop-In-Bibliotheksimport von {0}. library.import.error.notGM = Nur SL kann eine Drop-in-Bibliothek importieren. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Beschreibung library.dialog.addon.readMeFile = Read-Me-Datei anzeigen library.dialog.addon.authors = Autoren library.dialog.addon.license = Lizenz +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = Lizenzdatei anzeigen library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Soll die Add-on-Bibliothek {0} mit allen ihr library.dialog.copy.title = Das kopierte CSS dient nur Testzwecken.
Im Add-on benutze
lib\://net.rptools.maptool/css/mt-stat-sheet.css oder
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = CSS-Theme ins Clipboard kopieren library.dialog.copyMTStatSheetTheme = Stat Sheet-Theme ins Clipboard kopieren +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Kann {0} nicht in {1} konvertieren. From 19d963aa93e3af7571311ac33d5847479ef0c1c0 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:06 +0800 Subject: [PATCH 094/146] New translations i18n.properties (Italian) --- .../maptool/language/i18n_it_IT.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_it_IT.properties b/src/main/resources/net/rptools/maptool/language/i18n_it_IT.properties index 5c1eec73ba..9361c9cdb5 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_it_IT.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_it_IT.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Errore nell'esecuzione di "{0}"\: il nome del token o l'id "{1}" è sconosciuto sulla mappa "{2}". macro.function.general.wrongNumParam = La funzione "{0}" richiede esattamente {1} parametri; Ne sono stati forniti {2}. macro.function.general.listCannotBeEmpty = {0}\: la lista delle stringhe sull'argomento {1} non può essere vuota +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Tipo metrico "{0}" non valido. @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Definizione libreria non valida. library.error.emptyName = Il nome non può essere vuoto per la libreria aggiuntiva. library.error.emptyVersion = La versione non può essere vuota per la libreria aggiuntiva {0}. library.dialog.import.title = Importa Libreria Aggiuntiva. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = Errore di IO durante l'importazione della libreria Drop In. library.import.error = Errore nell'importazione della Libreria Drop In {0}. library.import.error.notGM = Solo il GM può importare librerie Drop In. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Descrizione library.dialog.addon.readMeFile = Leggi file Read Me library.dialog.addon.authors = Autori library.dialog.addon.license = Licenza +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = Visualizza File Licenza library.dialog.addon.giturl = Git URL library.dialog.addon.website = Sito @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Sei sicuro di voler rimuovere la libreria ag library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Impossibile convertire {0} in {1}. From 6ae551269f300cda066c0f3925ef062173c42dfd Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:07 +0800 Subject: [PATCH 095/146] New translations i18n.properties (Japanese) --- .../maptool/language/i18n_ja_JP.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties b/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties index c144ddd0ef..91f8cbe3d7 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = 『{0}』の実行時にエ macro.function.general.unknownTokenOnMap = 『{0}』の実行時にエラー。『{2}』の地図では、トークン名またはID『{1}』は不明です。 macro.function.general.wrongNumParam = 関数 {0} の引数は {1} 個でなければなりません。与えられた引数は {2} 個でした。 macro.function.general.listCannotBeEmpty = {0}: 引数『{1}』の文字列リストを空にするとこはできません。 +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = 計測タイプ {0} は無効です。 @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = ライブラリー定義が無効です。 library.error.emptyName = 拡張ライブラリーの名称は空白にできません。 library.error.emptyVersion = 拡張ライブラリー {0} のバージョンは空白にできません。 library.dialog.import.title = 拡張ライブラリーを取り込む。 +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = ドロップされたライブラリーの取り込み時に入出力エラーが発生しました。 library.import.error = ドロップされたライブラリー {0} の取り込み時に力エラーが発生しました。 library.import.error.notGM = ライブラリーをドロップして取込めるのはGMのみです。 @@ -2778,6 +2782,7 @@ library.dialog.addon.description = 説明 library.dialog.addon.readMeFile = 『はじめにお読みください』を表示 library.dialog.addon.authors = 作者 library.dialog.addon.license = 許諾 +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = 許諾ファイルを表示 library.dialog.addon.giturl = Git URL library.dialog.addon.website = ウェブサイト @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = 拡張ライブラリー『{0}』と、全 library.dialog.copy.title = コピーされたCSSはテストで使用する事を目的としています。
拡張ライブラリーでの使用
lib\://net.rptools.maptool/css/mt-stat-sheet.css または
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = CSSテーマをクリップボードにコピーする library.dialog.copyMTStatSheetTheme = データシートのテーマをクリップボードにコピーする +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = {0} を {1} に変換する事ができません。 From edeb7226c58ccfabf5daa1ee95838af05a469e95 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:09 +0800 Subject: [PATCH 096/146] New translations i18n.properties (Dutch) --- .../maptool/language/i18n_nl_NL.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_nl_NL.properties b/src/main/resources/net/rptools/maptool/language/i18n_nl_NL.properties index b4809fbf56..47867eee19 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_nl_NL.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_nl_NL.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Fout bij het uitvoeren van "{0}"\: de token naam of id "{1}" is onbekend op kaart "{2}". macro.function.general.wrongNumParam = Functie "{0}" vereist precies {1} parameters; {2} zijn geleverd. macro.function.general.listCannotBeEmpty = {0}\: string list at argument {1} cannot be empty +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Ongeldig metrisch type "{0}". @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 3acc038c12bec53a048ab32d4413d824acf179c6 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:10 +0800 Subject: [PATCH 097/146] New translations i18n.properties (Polish) --- .../maptool/language/i18n_pl_PL.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_pl_PL.properties b/src/main/resources/net/rptools/maptool/language/i18n_pl_PL.properties index 418d3b2f85..d889da57cd 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_pl_PL.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_pl_PL.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Blad podczas wykonywania {0 macro.function.general.unknownTokenOnMap = Błąd przy wykonywaniu „{0}”\: nazwa lub ID żetonu „{1}” nie są znane na mapie „{2}”. macro.function.general.wrongNumParam = Funkcja {0} wymaga dokladnie {1} parametetÛw; {2} jest podanych. macro.function.general.listCannotBeEmpty = {0}\: lista znaków dla argumentu {1} nie może być pusta +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Invalid Metric type {0}. @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Nieprawidłowa definicja biblioteki. library.error.emptyName = Nazwa nie może być pusta dla biblioteki dodatków. library.error.emptyVersion = Wersja nie może być pusta dla biblioteki dodatków {0}. library.dialog.import.title = Importuj bibliotekę dodatków. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = Błąd IO importu biblioteki Drop In. library.import.error = Błąd importu biblioteki Drop In {0}. library.import.error.notGM = Tylko GM może importować Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Opis library.dialog.addon.readMeFile = Zobacz plik ReadMe library.dialog.addon.authors = Autorzy library.dialog.addon.license = Licencja +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = Pokaż licencję library.dialog.addon.giturl = Git URL library.dialog.addon.website = Strona WWW @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Czy na pewno chcesz usunąć bibliotekę dod library.dialog.copy.title = Skopiowany CSS służy wyłącznie do testowania.
W ramach dodatku użyj
lib\://net.rptools.maptool/css/mt-stat-sheet.css lub
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Skopiuj motyw CSS do clipbaord library.dialog.copyMTStatSheetTheme = Skopiuj motyw arkusza statystyk do clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Nie można przekonwertować {0} na {1}. From 0ce7b3d43ec8b8f413763e103dc5746c852df1a3 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:12 +0800 Subject: [PATCH 098/146] New translations i18n.properties (Russian) --- .../maptool/language/i18n_ru_RU.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_ru_RU.properties b/src/main/resources/net/rptools/maptool/language/i18n_ru_RU.properties index feea347de8..1f0de31cbc 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_ru_RU.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_ru_RU.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Ошибка выполнения "{0}"\: имя или id токена "{1}" неизвестно на карте "{2}". macro.function.general.wrongNumParam = Функция "{0}" требует ровно {1} параметра/-ов; было предоставлено\: {2}. macro.function.general.listCannotBeEmpty = {0}\: список строк аргумента {1} не может быть пустым +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Недопустимый тип метрики "{0}". @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 590b3dfa9f76713195967edc4373e1908ef5f7e8 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:13 +0800 Subject: [PATCH 099/146] New translations i18n.properties (Swedish) --- .../maptool/language/i18n_sv_SE.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_sv_SE.properties b/src/main/resources/net/rptools/maptool/language/i18n_sv_SE.properties index fe994dac94..48575923db 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_sv_SE.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_sv_SE.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Fel vid körning av "{0}"\: token namn eller id "{1}" är okänd på kartan "{2}". macro.function.general.wrongNumParam = Funktion "{0}" kräver exakt {1} parametrar; {2} har angetts. macro.function.general.listCannotBeEmpty = {0}\: stränglista i argument {1} kan inte vara tom +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Ogiltig metrisk typ "{0}". @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From a66ff09a15f00d58e4b99530b04560900b99ff4d Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:15 +0800 Subject: [PATCH 100/146] New translations i18n.properties (Ukrainian) --- .../maptool/language/i18n_uk_UA.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_uk_UA.properties b/src/main/resources/net/rptools/maptool/language/i18n_uk_UA.properties index 78cc69c18f..cca9eea402 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_uk_UA.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_uk_UA.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Error executing "{0}"\: the token name or id "{1}" is unknown on map "{2}". macro.function.general.wrongNumParam = Function "{0}" requires exactly {1} parameters; {2} were provided. macro.function.general.listCannotBeEmpty = {0}\: string list at argument {1} cannot be empty +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Invalid metric type "{0}". @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From f26b12d391ed6d9b9ab61766cbd5b8d51f639deb Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:16 +0800 Subject: [PATCH 101/146] New translations i18n.properties (Chinese Simplified) --- .../maptool/language/i18n_zh_CN.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_zh_CN.properties b/src/main/resources/net/rptools/maptool/language/i18n_zh_CN.properties index 4ca9d5191a..09feca624f 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_zh_CN.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_zh_CN.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = 执行"{0}"错误:在地图"{2}"上指示物名称或id"{1}"未知。 macro.function.general.wrongNumParam = 函数"{0}"需要 {1} 项参数,但提供了 {2} 项。 macro.function.general.listCannotBeEmpty = {0}\: 字符串列表的参数 {1} 不能为空 +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = 距离度量方法"{0}"非法。 @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = 无效的库定义。 library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = 导入附加组件库。 +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 06a91e73493fddb5f0eb53f531b59be9ebc2f23b Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:18 +0800 Subject: [PATCH 102/146] New translations i18n.properties (Portuguese, Brazilian) --- .../maptool/language/i18n_pt_BR.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_pt_BR.properties b/src/main/resources/net/rptools/maptool/language/i18n_pt_BR.properties index 213a6f81f7..7bffb6fb5d 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_pt_BR.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_pt_BR.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Error executing "{0}"\: the token name or id "{1}" is unknown on map "{2}". macro.function.general.wrongNumParam = Function "{0}" requires exactly {1} parameters; {2} were provided. macro.function.general.listCannotBeEmpty = {0}\: string list at argument {1} cannot be empty +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Invalid metric type "{0}". @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Autores library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 77386756803be0e0d28322fec0872073f10e14b9 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:19 +0800 Subject: [PATCH 103/146] New translations i18n.properties (English, Australia) --- .../maptool/language/i18n_en_AU.properties | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties b/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties index 5a8aad2044..59505c1c97 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Error executing "{0}"\: the token name or id "{1}" is unknown on map "{2}". macro.function.general.wrongNumParam = Function "{0}" requires exactly {1} parameters; {2} were provided. macro.function.general.listCannotBeEmpty = {0}\: string list at argument {1} cannot be empty +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Invalid metric type "{0}". @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2786,8 +2791,41 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 3cb771c83d3740437911e54c76e198a68a91d44b Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:21 +0800 Subject: [PATCH 104/146] New translations i18n.properties (English, United Kingdom) --- .../maptool/language/i18n_en_GB.properties | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties b/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties index 09d93d7d9f..06b2802bda 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties @@ -1198,7 +1198,7 @@ action.error.noMapBoard = There is no map image. Use "Edit # hard-coded in the application, but that's OSX for you. ;-) action.exit = E&xit action.exit.description = Exit out of MapTool. -action.exportCampaignAs = +action.exportCampaignAs = action.exportCampaignAs.description = Export current campaign to a version compatible with older MapTool releases. action.exportScreenShot = Screenshot action.exportScreenShot.title = Export Screenshot @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Error executing "{0}"\: the token name or id "{1}" is unknown on map "{2}". macro.function.general.wrongNumParam = Function "{0}" requires exactly {1} parameters; {2} were provided. macro.function.general.listCannotBeEmpty = {0}\: string list at argument {1} cannot be empty +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Invalid metric type "{0}". @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2786,8 +2791,41 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 63edb7294b106367008f7b7ba42ad08ff7a09ca0 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:13:22 +0800 Subject: [PATCH 105/146] New translations i18n.properties (Sinhala) --- .../maptool/language/i18n_si_LK.properties | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_si_LK.properties b/src/main/resources/net/rptools/maptool/language/i18n_si_LK.properties index 0945e5e313..6265fd77c3 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_si_LK.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_si_LK.properties @@ -1877,6 +1877,8 @@ macro.function.general.unknownProperty = Error executing "{0}"\: th macro.function.general.unknownTokenOnMap = Error executing "{0}"\: the token name or id "{1}" is unknown on map "{2}". macro.function.general.wrongNumParam = Function "{0}" requires exactly {1} parameters; {2} were provided. macro.function.general.listCannotBeEmpty = {0}\: string list at argument {1} cannot be empty +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Invalid metric type "{0}". @@ -2740,6 +2742,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2778,6 +2782,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2788,6 +2793,39 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 494de290718fdba9e543313952641813bc9a3f57 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:34:25 +0800 Subject: [PATCH 106/146] Update source file i18n.properties --- .../rptools/maptool/language/i18n.properties | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index e28a6e524e..09fb8740fb 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2013,6 +2013,8 @@ macro.function.general.unknownProperty = Error executing "{0}": the macro.function.general.unknownTokenOnMap = Error executing "{0}": the token name or id "{1}" is unknown on map "{2}". macro.function.general.wrongNumParam = Function "{0}" requires exactly {1} parameters; {2} were provided. macro.function.general.listCannotBeEmpty = {0}: string list at argument {1} cannot be empty +macro.function.general.wrongParamType = A parameter is the wrong type. +macro.function.general.paramCannotBeEmpty = A parameter is empty. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Invalid metric type "{0}". @@ -2929,6 +2931,8 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can''t be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. +library.dialog.reimport = Reimport +library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2970,6 +2974,7 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License +library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2980,8 +2985,41 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing \ purposes only.
Within your add-on use
lib://net.rptools.maptool/css/mt-stat-sheet.css \ or
lib://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.externalAddon = Enable external add-on libraries (development purposes only) +library.dialog.addon.subdir = Sub Directory +library.dialog.addon.imported = Imported +library.dialog.addon.updated = Updated +library.dialog.addon.refresh = Refresh +library.dialog.addon.createNew = Create New Add-On +library.dialog.addon.createMTLib = Create .mtlib file +library.dialog.directory.label = Add On Directory +library.dialog.parentdir.label = Parent Directory +library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format +library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. +library.dialog.addon.createExample=Create Example Files +library.dialog.addon.createEvents=Events +library.dialog.addons.authorsComma=Authors (comma seperated) +library.dialog.addon.createSlash=Slash Commands +library.dialog.addon.createMTSProps=Macro Script Properties +library.dialog.addon.udf=User Defined Functions +library.dialog.addon.create.readeMe = ### Read Me\nPlace your Read Me information here, this file supports markup. +library.dialog.addon.create.licenseText = Place your license details in this file. +library.dialog.addon.create.shortDesc = Add-on Short Description +library.dialog.addon.create.longDesc = Add-on Long Description +library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. +library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. +library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On +library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. +library.dialog.failedToCreateFile = Failed to create file {0} for add-on. +library.dialog.create.exampleSlashCmdDesc = Example Slash Command +library.dialog.create.autoExecDesc = Auto Exec in macro link example +library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example +library.dialog.error.displayingAddons = Error displaying add-on libraries +library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) +library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file +library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can''t convert {0} to {1}. From 6cf7d758efb24a4d526630afdcc12105536a61ab Mon Sep 17 00:00:00 2001 From: Craig Wisniewski Date: Thu, 17 Oct 2024 19:18:05 +1030 Subject: [PATCH 107/146] Revert "Provide functionality for add-on development" --- build.gradle | 3 - .../maptool/client/AppPreferences.java | 9 - .../ui/addon/AddOnLibrariesDialogView.form | 206 ++------- .../ui/addon/AddOnLibrariesDialogView.java | 300 ++---------- .../ui/addon/AddOnLibrariesTableModel.java | 38 +- .../client/ui/addon/CreateNewAddonDialog.form | 328 ------------- .../client/ui/addon/CreateNewAddonDialog.java | 259 ----------- .../addon/ExternalAddOnImportCellEditor.java | 89 ---- .../ExternalAddOnLibrariesTableModel.java | 99 ---- .../client/ui/addon/creator/MTLibCreator.java | 102 ---- .../client/ui/addon/creator/NewAddOn.java | 53 --- .../ui/addon/creator/NewAddOnBuilder.java | 248 ---------- .../ui/addon/creator/NewAddOnCreator.java | 335 -------------- .../CampaignPropertiesDialog.java | 5 +- .../library/ExternalAddonsUpdateEvent.java | 18 - .../maptool/model/library/LibraryManager.java | 91 +--- .../model/library/addon/AddOnLibrary.java | 92 +--- .../library/addon/AddOnLibraryImporter.java | 193 +------- .../library/addon/AddOnLibraryManager.java | 120 +---- .../addon/ExternalAddOnLibraryManager.java | 435 ------------------ .../library/addon/ExternalLibraryInfo.java | 36 -- .../net/rptools/maptool/util/FileUtil.java | 39 -- .../rptools/maptool/language/i18n.properties | 40 +- .../maptool/language/i18n_cs.properties | 4 +- .../maptool/language/i18n_da.properties | 4 +- .../maptool/language/i18n_en.properties | 4 +- .../maptool/language/i18n_en_AU.properties | 4 +- .../maptool/language/i18n_en_GB.properties | 6 +- .../maptool/language/i18n_es.properties | 4 +- .../maptool/language/i18n_fr.properties | 4 +- .../maptool/language/i18n_it.properties | 4 +- .../maptool/language/i18n_nl.properties | 4 +- .../maptool/language/i18n_pl.properties | 4 +- .../maptool/language/i18n_pt.properties | 4 +- .../maptool/language/i18n_ru.properties | 4 +- .../maptool/language/i18n_si.properties | 4 +- .../maptool/language/i18n_sv.properties | 4 +- .../maptool/language/i18n_uk.properties | 4 +- .../maptool/language/i18n_zh.properties | 4 +- 39 files changed, 159 insertions(+), 3045 deletions(-) delete mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.form delete mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java delete mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java delete mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java delete mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/creator/MTLibCreator.java delete mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java delete mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java delete mode 100644 src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java delete mode 100644 src/main/java/net/rptools/maptool/model/library/ExternalAddonsUpdateEvent.java delete mode 100644 src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java delete mode 100644 src/main/java/net/rptools/maptool/model/library/addon/ExternalLibraryInfo.java diff --git a/build.gradle b/build.gradle index 9707e79bc3..d3bfe5e7d6 100644 --- a/build.gradle +++ b/build.gradle @@ -485,9 +485,6 @@ dependencies { // Built In Add-on Libraries implementation 'com.github.RPTools:maptool-builtin-addons:1.3' - // File watcher library to work around some inconsistencies with java.nio.file.WatchService - implementation 'io.methvin:directory-watcher:0.18.0' - // For advanced dice roller implementation 'com.github.RPTools:advanced-dice-roller:1.0.3' } diff --git a/src/main/java/net/rptools/maptool/client/AppPreferences.java b/src/main/java/net/rptools/maptool/client/AppPreferences.java index e46ae34e66..ff458fbe5b 100644 --- a/src/main/java/net/rptools/maptool/client/AppPreferences.java +++ b/src/main/java/net/rptools/maptool/client/AppPreferences.java @@ -393,15 +393,6 @@ public class AppPreferences { public static final Preference iconTheme = StringType.create("iconTheme", "Rod Takehara"); - public static final Preference externalAddOnLibrariesPath = - StringType.create("externalAddOnLibrariesPath", null); - - public static final Preference externalAddOnLibrariesEnabled = - BooleanType.create("externalAddOnLibrariesEnabled", false); - - public static final Preference createAddOnParentDir = - StringType.create("createAddOnParentDir", System.getProperty("user.home")); - static { // Used to be stored as separate components but now is one color. Add if not already there. if (prefs.get("trustedPrefixFG", null) == null) { diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form index 42ba1f8bfb..e27669d642 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.form @@ -1,26 +1,31 @@
- + + - - + - + + + + + + - - - + + + @@ -36,8 +41,8 @@ - - + + @@ -50,19 +55,15 @@ - + - + - - - - - + @@ -187,7 +188,6 @@ - @@ -275,7 +275,7 @@ - + @@ -283,146 +283,40 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -431,7 +325,9 @@ - + + + diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java index 5ce4cc77c2..75137f8120 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesDialogView.java @@ -16,6 +16,8 @@ import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -25,7 +27,6 @@ import java.util.Optional; import java.util.concurrent.ExecutionException; import javax.swing.JButton; -import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFileChooser; @@ -33,155 +34,52 @@ import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.JTable; -import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.KeyStroke; -import javax.swing.filechooser.FileNameExtensionFilter; import net.rptools.maptool.client.AppActions.MapPreviewFileChooser; import net.rptools.maptool.client.AppConstants; -import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.swing.SwingUtil; import net.rptools.maptool.client.ui.JLabelHyperLinkListener; import net.rptools.maptool.client.ui.ViewAssetDialog; -import net.rptools.maptool.client.ui.addon.creator.MTLibCreator; -import net.rptools.maptool.events.MapToolEventBus; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.library.Library; import net.rptools.maptool.model.library.LibraryInfo; import net.rptools.maptool.model.library.LibraryManager; import net.rptools.maptool.model.library.addon.AddOnLibraryImporter; -import net.rptools.maptool.model.library.addon.ExternalLibraryInfo; /** Dialog for managing add-on libraries. */ public class AddOnLibrariesDialogView extends JDialog { - /** Removes an add-on library. */ + private JPanel contentPane; private JButton buttonRemove; - - /** Closes the dialog. */ private JButton buttonClose; - - /** The tabbed pane for the dialog. */ private JTabbedPane tabbedPane; - - /** Table containing the imported add-ons */ private JTable addOnLibraryTable; - - /** The text pane for the add-on description. */ private JTextPane addOnDescriptionTextPane; - - /** Adds an add-on library. */ private JButton buttonAdd; - - /** The name of the selected add-on. */ private JLabel addOnNameLabel; - - /** The version of the selected add-on. */ private JLabel addOnVersionLabel; - - /** The authors of the selected add-on. */ private JLabel addOnAuthorsLabel; - - /** The namespace of the selected add-on. */ private JLabel addOnNamespaceLabel; - - /** The short description of the selected add-on. */ private JLabel addOnShortDescLabel; - - /** The website of the selected add-on. */ private JLabel addOnWebsiteLabel; - - /** The git URL of the selected add-on. */ private JLabel addOnGitUrlLabel; - - /** The license of the selected add-on. */ private JLabel addOnLicenseLabel; - - /** The button for viewing the README file of the selected add-on. */ private JButton viewReadMeFileButton; - - /** The button for viewing the license file of the selected add-on. */ private JButton viewLicenseFileButton; - - /** The button for copying the theme CSS. */ private JButton copyThemeCSS; - - /** The button for copying the stat sheet theme. */ private JButton copyStatSheetThemeButton; - /** The checkbox for enabling external add-ons. */ - private JCheckBox enableExternalAddOnCheckBox; - - /** The table for external add-ons. */ - private JTable externalAddonTable; - - /** The button for creating an add-on skeleton. */ - private JButton createAddonSkeletonButton; - - /** The text field for the add-on development directory. */ - private JTextField directoryTextField; - - /** The button for browsing to select the add-on development directory. */ - private JButton browseButton; - - /** The content pane for the dialog. */ - private JPanel contentPane; - - /** Creates a .mtlib (zip) file for the selected add-on. */ - private JButton createMTLibButton; - - /** The information for the selected add-on. */ private LibraryInfo selectedAddOn; - /** The model for the external add-on libraries table. */ - private final ExternalAddOnLibrariesTableModel externalAddOnLibrariesTableModel; - - /** The model for the add-on libraries table. */ - private final AddOnLibrariesTableModel addOnLibrariesTableModel; - - /** The currently selected external add-on. */ - private ExternalLibraryInfo selectedExternalAddon; - /** Creates a new instance of the dialog. */ public AddOnLibrariesDialogView() { setContentPane(contentPane); setModal(true); getRootPane().setDefaultButton(buttonClose); - - var eventBus = new MapToolEventBus().getMainEventBus(); - - addOnLibrariesTableModel = new AddOnLibrariesTableModel(); - eventBus.register(addOnLibrariesTableModel); - - addOnLibraryTable.setModel(addOnLibrariesTableModel); + addOnLibraryTable.setModel(new AddOnLibrariesTableModel()); addOnLibraryTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - externalAddOnLibrariesTableModel = new ExternalAddOnLibrariesTableModel(); - externalAddonTable.setModel(externalAddOnLibrariesTableModel); - eventBus.register(externalAddOnLibrariesTableModel); - - externalAddonTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - externalAddonTable.setDefaultRenderer( - ExternalLibraryInfo.class, new ExternalAddOnImportCellEditor()); - externalAddonTable.setDefaultEditor( - ExternalLibraryInfo.class, new ExternalAddOnImportCellEditor()); - externalAddonTable - .getSelectionModel() - .addListSelectionListener( - e -> { - int selectedRow = externalAddonTable.getSelectedRow(); - if (selectedRow == -1) { - createMTLibButton.setEnabled(false); - selectedExternalAddon = null; - } else { - createMTLibButton.setEnabled(true); - selectedExternalAddon = - (ExternalLibraryInfo) - externalAddOnLibrariesTableModel.getValueAt(selectedRow, 6); - } - }); - buttonRemove.setEnabled(false); addOnLibraryTable .getSelectionModel() @@ -232,7 +130,11 @@ public void windowClosing(WindowEvent e) { // call onClose() on ESCAPE contentPane.registerKeyboardAction( - e -> onClose(), + new ActionListener() { + public void actionPerformed(ActionEvent e) { + onClose(); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); @@ -267,128 +169,42 @@ public void windowClosing(WindowEvent e) { }); copyThemeCSS.addActionListener( - e -> - new LibraryManager() - .getLibrary(AppConstants.MT_BUILTIN_ADD_ON_NAMESPACE) - .ifPresent( - library -> { - URI uri = URI.create(AppConstants.MT_THEME_CSS); - String themeCss = null; - try { - themeCss = library.readAsString(uri.toURL()).get(); - } catch (InterruptedException | ExecutionException | IOException ex) { - throw new RuntimeException(ex); - } - Toolkit.getDefaultToolkit() - .getSystemClipboard() - .setContents(new StringSelection(themeCss), null); - })); - - copyStatSheetThemeButton.addActionListener( - e -> - new LibraryManager() - .getLibrary(AppConstants.MT_BUILTIN_ADD_ON_NAMESPACE) - .ifPresent( - library -> { - URI uri = URI.create(AppConstants.MT_THEME_STAT_SHEET_CSS); - String themeCss = null; - try { - themeCss = library.readAsString(uri.toURL()).get(); - } catch (InterruptedException | ExecutionException | IOException ex) { - throw new RuntimeException(ex); - } - Toolkit.getDefaultToolkit() - .getSystemClipboard() - .setContents(new StringSelection(themeCss), null); - })); - - createAddonSkeletonButton.addActionListener(e -> createAddonSkeleton()); - - enableExternalAddOnCheckBox.addActionListener( e -> { - setExternalAddOnControlsEnabled(enableExternalAddOnCheckBox.isSelected()); - directoryTextField.setEnabled(enableExternalAddOnCheckBox.isSelected()); - try { - new LibraryManager() - .setExternalLibrariesEnabled(enableExternalAddOnCheckBox.isSelected()); - } catch (IOException ex) { - // do nothing - } - AppPreferences.externalAddOnLibrariesEnabled.set( - enableExternalAddOnCheckBox.isSelected()); - }); - - browseButton.addActionListener( - e -> { - JFileChooser chooser = new JFileChooser(); - chooser.setDialogTitle(I18N.getText("library.dialog.import.title")); - chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - chooser.showOpenDialog(MapTool.getFrame()); - if (chooser.getSelectedFile() != null) { - directoryTextField.setText(chooser.getSelectedFile().getAbsolutePath()); - AppPreferences.externalAddOnLibrariesPath.set( - chooser.getSelectedFile().getAbsolutePath()); - } + new LibraryManager() + .getLibrary(AppConstants.MT_BUILTIN_ADD_ON_NAMESPACE) + .ifPresent( + library -> { + URI uri = URI.create(AppConstants.MT_THEME_CSS); + String themeCss = null; + try { + themeCss = library.readAsString(uri.toURL()).get(); + } catch (InterruptedException | ExecutionException | IOException ex) { + throw new RuntimeException(ex); + } + Toolkit.getDefaultToolkit() + .getSystemClipboard() + .setContents(new StringSelection(themeCss), null); + }); }); - createMTLibButton.addActionListener( + copyStatSheetThemeButton.addActionListener( e -> { - if (selectedExternalAddon == null) { - return; - } - - var fileChooser = new JFileChooser(); - fileChooser.setFileFilter( - new FileNameExtensionFilter( - I18N.getText("library.dialog.addon.fileFilter"), "mtlib")); - if (fileChooser.showSaveDialog(MapTool.getFrame()) == JFileChooser.APPROVE_OPTION) { - var outputPath = fileChooser.getSelectedFile().toPath(); - if (!outputPath.toString().endsWith(".mtlib")) { - outputPath = outputPath.resolveSibling(outputPath.getFileName() + ".mtlib"); - } - var creator = - new MTLibCreator( - selectedExternalAddon.backingDirectory(), - outputPath.getParent(), - outputPath.getFileName().toString()); - creator.create(); - } + new LibraryManager() + .getLibrary(AppConstants.MT_BUILTIN_ADD_ON_NAMESPACE) + .ifPresent( + library -> { + URI uri = URI.create(AppConstants.MT_THEME_STAT_SHEET_CSS); + String themeCss = null; + try { + themeCss = library.readAsString(uri.toURL()).get(); + } catch (InterruptedException | ExecutionException | IOException ex) { + throw new RuntimeException(ex); + } + Toolkit.getDefaultToolkit() + .getSystemClipboard() + .setContents(new StringSelection(themeCss), null); + }); }); - - createMTLibButton.setEnabled(false); - - LibraryManager libraryManager = new LibraryManager(); - enableExternalAddOnCheckBox.setSelected(libraryManager.externalLibrariesEnabled()); - directoryTextField.setText(AppPreferences.externalAddOnLibrariesPath.get()); - setExternalAddOnControlsEnabled(enableExternalAddOnCheckBox.isSelected()); - if (enableExternalAddOnCheckBox.isSelected()) { - refreshLibraries(); - } - pack(); - } - - /** Refreshes the external add-on libraries table information. */ - private void refreshLibraries() { - var model = (ExternalAddOnLibrariesTableModel) externalAddonTable.getModel(); - model.refresh(); - } - - /** - * Sets the enabled state of the external add-on controls. - * - * @param selected the state to set. - */ - private void setExternalAddOnControlsEnabled(boolean selected) { - externalAddonTable.setEnabled(selected); - browseButton.setEnabled(selected); - } - - /** Creates a new add-on skeleton. */ - private void createAddonSkeleton() { - var dialog = new CreateNewAddonDialog(); - dialog.pack(); - SwingUtil.centerOver(dialog, MapTool.getFrame()); - dialog.setVisible(true); } /** @@ -430,9 +246,6 @@ private void selectedAddOnChanged(LibraryInfo addOn) { /** Closes the dialog. */ private void onClose() { dispose(); - var eventBus = new MapToolEventBus().getMainEventBus(); - eventBus.unregister(externalAddOnLibrariesTableModel); - eventBus.unregister(addOnLibrariesTableModel); } /** Add an add-on library to the library manager. */ @@ -462,11 +275,6 @@ private void addAddOnLibrary() { } } - /** - * Views the license file for the given library. - * - * @param libInfo the library to view the license file for. - */ private void viewLicenseFile(LibraryInfo libInfo) { Optional lib = new LibraryManager().getLibrary(libInfo.namespace()); lib.ifPresent( @@ -476,20 +284,9 @@ private void viewLicenseFile(LibraryInfo libInfo) { .thenAccept( a -> a.ifPresent( - asset -> - new ViewAssetDialog( - asset, - I18N.getText("library.dialog.addon.license"), - 640, - 480) - .showModal()))); + asset -> new ViewAssetDialog(asset, "License", 640, 480).showModal()))); } - /** - * Views the README file for the given library. - * - * @param libInfo the library to view the README file for. - */ private void viewReadMeFile(LibraryInfo libInfo) { Optional lib = new LibraryManager().getLibrary(libInfo.namespace()); lib.ifPresent( @@ -499,12 +296,13 @@ private void viewReadMeFile(LibraryInfo libInfo) { .thenAccept( a -> a.ifPresent( - asset -> - new ViewAssetDialog( - asset, - I18N.getText("library.dialog.addon.readme"), - 640, - 480) - .showModal()))); + asset -> new ViewAssetDialog(asset, "License", 640, 480).showModal()))); + } + + public static void main(String[] args) { + AddOnLibrariesDialogView dialog = new AddOnLibrariesDialogView(); + dialog.pack(); + dialog.setVisible(true); + System.exit(0); } } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java index 579ae81f9c..cdc5e83822 100644 --- a/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/addon/AddOnLibrariesTableModel.java @@ -14,46 +14,30 @@ */ package net.rptools.maptool.client.ui.addon; -import com.google.common.eventbus.Subscribe; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import javax.swing.table.AbstractTableModel; import net.rptools.maptool.language.I18N; -import net.rptools.maptool.model.library.AddOnsAddedEvent; import net.rptools.maptool.model.library.LibraryInfo; import net.rptools.maptool.model.library.LibraryManager; import net.rptools.maptool.model.library.LibraryType; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -/** - * The AddOnLibrariesTableModel class is a table model for displaying add-on libraries in the a - * JTable. - */ public class AddOnLibrariesTableModel extends AbstractTableModel { - - /** The logger. */ private static final Logger log = LogManager.getLogger(AddOnLibrariesDialogController.class); - /** The list of add-on libraries. */ private final List addons = new ArrayList<>(); - /** The AddOnLibrariesTableModel constructor. */ public AddOnLibrariesTableModel() { try { addons.addAll(new LibraryManager().getLibraries(LibraryType.ADD_ON)); } catch (ExecutionException | InterruptedException e) { - log.error(I18N.getText("library.dialog.error.displayingAddons"), e); + log.error("Error displaying add-on libraries", e); } } - /** - * Get the add-on library at the specified row of the table. - * - * @param row the row. - * @return the add-on library. - */ public LibraryInfo getAddOn(int row) { return addons.get(row); } @@ -101,24 +85,4 @@ public void fireTableDataChanged() { } super.fireTableDataChanged(); } - - /** - * Handle the AddOnsUpdatedEvent event by firing a table data changed event. - * - * @param event the AddOnsUpdatedEvent event. - */ - @Subscribe - public void handleAddOnsUpdatedEvent(AddOnsAddedEvent event) { - fireTableDataChanged(); - } - - /** - * Handle the AddOnsRemovedEvent event by firing a table data changed event. - * - * @param event the AddOnsRemovedEvent event. - */ - @Subscribe - public void handleAddOnsRemovedEvent(AddOnsAddedEvent event) { - fireTableDataChanged(); - } } diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.form b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.form deleted file mode 100644 index 949190019e..0000000000 --- a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.form +++ /dev/null @@ -1,328 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java b/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java deleted file mode 100644 index 79e34f4db1..0000000000 --- a/src/main/java/net/rptools/maptool/client/ui/addon/CreateNewAddonDialog.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.ui.addon; - -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.File; -import java.io.IOException; -import java.util.List; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JFileChooser; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTextField; -import javax.swing.JTextPane; -import javax.swing.KeyStroke; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import net.rptools.maptool.client.AppPreferences; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.addon.creator.NewAddOnBuilder; -import net.rptools.maptool.client.ui.addon.creator.NewAddOnCreator; -import net.rptools.maptool.language.I18N; - -/** Dialog for creating a new Add-On library skeleton. */ -public class CreateNewAddonDialog extends JDialog { - - /** The content pane. */ - private JPanel contentPane; - - /** OK button which will create the new Add-On. */ - private JButton buttonOK; - - /** Cancel button which will close the dialog. */ - private JButton buttonCancel; - - /** Text field for the Add-On name. */ - private JTextField nameTextField; - - /** Text field for the Add-On version. */ - private JTextField versionTextField; - - /** Check box for creating events. */ - private JCheckBox eventsCheckBox; - - /** Text pane for the Add-On description. */ - private JTextPane descriptionTextPane; - - /** Button for browsing for the directory to create the new add-on in. */ - private JButton directoryBrowseButton; - - /** Text field for the Add-On namespace. */ - private JTextField namespaceTextField; - - /** Text field for the Git URL. */ - private JTextField gitURLTextField; - - /** Text field for the website URL. */ - private JTextField websiteTextField; - - /** Text field for the license. */ - private JTextField licenseTextField; - - /** Text field for the authors. */ - private JTextField authorsTextField; - - /** Text field for the parent directory. */ - private JTextField parentDirectoryTextField; - - /** Check box for creating slash commands. */ - private JCheckBox slashCommandCheckBox; - - /** Check box for creating MTS properties. */ - private JCheckBox mtsPropCheckBox; - - /** Check box for creating UDFs. */ - private JCheckBox udfCheckBox; - - /** Text field for the short description. */ - private JTextField shortDescTextBox; - - /** Text field for the directory name. */ - private JTextField directoryTextField; - - /** Creates a new CreateNewAddonDialog. */ - public CreateNewAddonDialog() { - setContentPane(contentPane); - setModal(true); - getRootPane().setDefaultButton(buttonOK); - - buttonOK.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - onOK(); - } - }); - - buttonCancel.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - onCancel(); - } - }); - - // call onCancel() when cross is clicked - setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - addWindowListener( - new WindowAdapter() { - public void windowClosing(WindowEvent e) { - onCancel(); - } - }); - - // call onCancel() on ESCAPE - contentPane.registerKeyboardAction( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - onCancel(); - } - }, - KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); - - // browse button - directoryBrowseButton.addActionListener( - e -> { - JFileChooser chooser = new JFileChooser(); - chooser.setDialogTitle(I18N.getText("library.dialog.create.parentDir.title")); - chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - chooser.showOpenDialog(MapTool.getFrame()); - if (chooser.getSelectedFile() != null) { - var path = chooser.getSelectedFile().getAbsolutePath(); - parentDirectoryTextField.setText(path); - AppPreferences.createAddOnParentDir.set(path); - } - }); - - var docChangeListener = - new DocumentListener() { - @Override - public void insertUpdate(DocumentEvent e) { - validateInputs(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - validateInputs(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - validateInputs(); - } - }; - - namespaceTextField.getDocument().addDocumentListener(docChangeListener); - nameTextField.getDocument().addDocumentListener(docChangeListener); - versionTextField.getDocument().addDocumentListener(docChangeListener); - parentDirectoryTextField.getDocument().addDocumentListener(docChangeListener); - directoryTextField.getDocument().addDocumentListener(docChangeListener); - authorsTextField.getDocument().addDocumentListener(docChangeListener); - licenseTextField.getDocument().addDocumentListener(docChangeListener); - shortDescTextBox.getDocument().addDocumentListener(docChangeListener); - descriptionTextPane.getDocument().addDocumentListener(docChangeListener); - - setDefaults(); - - validateInputs(); - } - - /** Sets the default values for the new add-on fields. */ - private void setDefaults() { - namespaceTextField.setText("net.some-example.addon"); - nameTextField.setText("Example Add-On"); - versionTextField.setText("0.0.1"); - shortDescTextBox.setText(I18N.getText("library.dialog.addon.create.shortDesc")); - descriptionTextPane.setText(I18N.getText("library.dialog.addon.create.longDesc")); - parentDirectoryTextField.setText(AppPreferences.createAddOnParentDir.get()); - } - - /** Validates the input fields. */ - private void validateInputs() { - buttonOK.setEnabled( - !namespaceTextField.getText().isEmpty() - && !nameTextField.getText().isEmpty() - && !versionTextField.getText().isEmpty() - && !parentDirectoryTextField.getText().isEmpty() - && !directoryTextField.getText().isEmpty() - && !authorsTextField.getText().isEmpty()); - } - - /** - * Handles the OK button click. This will attempt to create the new add-on skeleton based on the - * users inputs and then close the dialog. - */ - private void onOK() { - var parentDir = new File(parentDirectoryTextField.getText()); - if (!parentDir.exists()) { - JOptionPane.showMessageDialog( - this, I18N.getText("library.dialog.addon.create.noSuchDir", parentDir.toString())); - return; - } - var dir = parentDir.toPath().resolve(directoryTextField.getText()).toFile(); - if (dir.exists()) { - JOptionPane.showMessageDialog( - this, I18N.getText("library.dialog.addon.create.dirExists", dir.toString())); - return; - } - - var newAddon = - new NewAddOnBuilder() - .setName(nameTextField.getText()) - .setVersion(versionTextField.getText()) - .setNamespace(namespaceTextField.getText()) - .setGitURL(gitURLTextField.getText()) - .setWebsite(websiteTextField.getText()) - .setLicense(licenseTextField.getText()) - .setShortDescription(shortDescTextBox.getText()) - .setDescription(descriptionTextPane.getText()) - .setAuthors(List.of(authorsTextField.getText().split(","))) - .setCreateEvents(eventsCheckBox.isSelected()) - .setCreateSlashCommands(slashCommandCheckBox.isSelected()) - .setCreateUDFs(udfCheckBox.isSelected()) - .setCreateMTSProperties(mtsPropCheckBox.isSelected()) - .build(); - dispose(); - try { - new NewAddOnCreator(newAddon, dir.toPath()).create(); - } catch (IOException e) { - JOptionPane.showMessageDialog(this, e.getMessage()); - } - } - - /** - * Handles the cancel button click. This closes the dialog without attempting to create the new - * add-on skeleton. - */ - private void onCancel() { - dispose(); - } -} diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java deleted file mode 100644 index e0c9722140..0000000000 --- a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnImportCellEditor.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.ui.addon; - -import java.awt.*; -import java.io.IOException; -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.table.TableCellEditor; -import javax.swing.table.TableCellRenderer; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.language.I18N; -import net.rptools.maptool.model.library.LibraryManager; -import net.rptools.maptool.model.library.addon.ExternalLibraryInfo; - -/** Cell editor for the import button in the external add-on dialog. */ -public class ExternalAddOnImportCellEditor extends AbstractCellEditor - implements TableCellEditor, TableCellRenderer { - - /** The button that is displayed in the cell. */ - private final JButton button = new JButton(); - - /** The external library info for the currently displayed row. */ - private ExternalLibraryInfo info; - - /** Creates a new cell editor. */ - public ExternalAddOnImportCellEditor() { - button.addActionListener( - e -> { - try { - new LibraryManager().importFromExternal(info.libraryInfo().namespace()); - } catch (IOException ex) { - MapTool.showError( - I18N.getText("library.dialog.import.failed", info.libraryInfo().namespace())); - } - stopCellEditing(); - }); - } - - @Override - public Component getTableCellEditorComponent( - JTable table, Object value, boolean isSelected, int row, int column) { - return getButton(table, value, isSelected, false, row, column); - } - - @Override - public Object getCellEditorValue() { - return info; - } - - @Override - public Component getTableCellRendererComponent( - JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - return getButton(table, value, isSelected, hasFocus, row, column); - } - - /** Returns the button component for the given cell. */ - private Component getButton( - JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - button.setEnabled(true); - info = (ExternalLibraryInfo) value; - String buttonTextKey; - if (info.isInstalled()) { - buttonTextKey = "library.dialog.reimport"; - } else { - buttonTextKey = "library.dialog.import"; - } - button.setText(I18N.getText(buttonTextKey)); - - var panel = new JPanel(); - panel.setLayout(new BorderLayout()); - panel.add(button, BorderLayout.CENTER); - panel.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); - panel.setBorder(new EmptyBorder(1, 5, 2, 5)); - return panel; - } -} diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java b/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java deleted file mode 100644 index a822cd33e6..0000000000 --- a/src/main/java/net/rptools/maptool/client/ui/addon/ExternalAddOnLibrariesTableModel.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.ui.addon; - -import com.google.common.eventbus.Subscribe; -import java.util.List; -import javax.swing.table.AbstractTableModel; -import net.rptools.maptool.language.I18N; -import net.rptools.maptool.model.library.ExternalAddonsUpdateEvent; -import net.rptools.maptool.model.library.LibraryManager; -import net.rptools.maptool.model.library.addon.ExternalLibraryInfo; - -/** - * The ExternalAddOnLibrariesTableModel class is a table model for the external add-on libraries. - */ -public class ExternalAddOnLibrariesTableModel extends AbstractTableModel { - - @Override - public int getRowCount() { - return new LibraryManager().getExternalAddOnLibraries().size(); - } - - @Override - public int getColumnCount() { - return 7; - } - - @Override - public Class getColumnClass(int columnIndex) { - return switch (columnIndex) { - case 4, 5 -> Boolean.class; - case 6 -> ExternalLibraryInfo.class; - default -> String.class; - }; - } - - @Override - public boolean isCellEditable(int rowIndex, int columnIndex) { - return getColumnClass(columnIndex) == ExternalLibraryInfo.class; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - List addons = new LibraryManager().getExternalAddOnLibraries(); - - var info = addons.get(rowIndex); - return switch (columnIndex) { - case 0 -> info.libraryInfo().name(); - case 1 -> info.libraryInfo().version(); - case 2 -> info.libraryInfo().namespace(); - case 3 -> info.subDirectoryName(); - case 4 -> info.isInstalled(); - case 5 -> info.updatedOnDisk(); - case 6 -> info; - default -> null; - }; - } - - @Override - public String getColumnName(int column) { - return switch (column) { - case 0 -> I18N.getText("library.dialog.addon.name"); - case 1 -> I18N.getText("library.dialog.addon.version"); - case 2 -> I18N.getText("library.dialog.addon.namespace"); - case 3 -> I18N.getText("library.dialog.addon.subdir"); - case 4 -> I18N.getText("library.dialog.addon.imported"); - case 5 -> I18N.getText("library.dialog.addon.updated"); - case 6 -> I18N.getText("library.dialog.addon.refresh"); - default -> null; - }; - } - - /** - * Handle the event when external addons are added. - * - * @param event the AddOnsAddedEvent event. - */ - @Subscribe - public void handleExternalAddOnsAdded(ExternalAddonsUpdateEvent event) { - refresh(); - } - - /** Refresh the table data. */ - public void refresh() { - fireTableDataChanged(); - } -} diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/MTLibCreator.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/MTLibCreator.java deleted file mode 100644 index 267446d9e8..0000000000 --- a/src/main/java/net/rptools/maptool/client/ui/addon/creator/MTLibCreator.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.ui.addon.creator; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.HashSet; -import java.util.zip.Deflater; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; -import net.rptools.maptool.client.MapTool; - -/** - * This class is responsible for creating a .mtlib file from the contents of a directory. - * - *

The .mtlib file is a zip file that contains all the files in the directory. - * - *

The .mtlib file is used to install the add-on into MapTool. - */ -public class MTLibCreator { - - /** The path to the directory to create the .mtlib file from. */ - private final Path addOnPath; - - /** The output path for the .mtlib file. */ - private final Path outputPath; - - /** The name of the .mtlib file to create. */ - private final String fileName; - - /** - * Create a new instance of the MTLibCreator. - * - * @param addOnPath The path to the directory to create the .mtlib file from. - * @param outputPath The output path for the .mtlib file. - * @param fileName The name of the .mtlib file to create. - */ - public MTLibCreator(Path addOnPath, Path outputPath, String fileName) { - this.addOnPath = addOnPath; - this.outputPath = outputPath; - this.fileName = fileName; - } - - /** Create the .mtlib file. */ - public void create() { - try (var zipOut = new ZipOutputStream(Files.newOutputStream(outputPath.resolve(fileName)))) { - zipOut.setLevel(Deflater.BEST_COMPRESSION); - var fileList = new HashSet(); - try (var pathStream = Files.find(addOnPath, Integer.MAX_VALUE, this::includeFile)) { - pathStream.forEach(fileList::add); - } - for (Path path : fileList) { - zipOut.putNextEntry(new ZipEntry(addOnPath.relativize(path).toString())); - Files.copy(path, zipOut); - } - } catch (Exception e) { - MapTool.showError("library.dialog.addon.errorCreatingMTLib", e); - } - } - - /** - * Determine if a file should be included in the .mtlib file. - * - * @param path The path to the file. - * @param att The attributes of the file. - * @return True if the file should be included, false otherwise. - */ - private boolean includeFile(Path path, BasicFileAttributes att) { - var subPath = addOnPath.relativize(path); - - if (att.isDirectory()) { - return false; - } - - if (subPath.getNameCount() == 0) { - return false; - } - - if (subPath.getName(0).toString().startsWith(".")) { - return false; - } - - if (path.getFileName().toString().toLowerCase().endsWith(".mtlib")) { - return false; - } - - return true; - } -} diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java deleted file mode 100644 index 367195e97c..0000000000 --- a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOn.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.ui.addon.creator; - -import java.util.List; - -/** - * Represents a new AddOn to be created. - * - * @param name The name of the AddOn. - * @param version The version of the AddOn. - * @param namespace The namespace of the AddOn. - * @param gitURL The git URL of the AddOn. - * @param website The website of the AddOn. - * @param license The license of the AddOn. - * @param shortDescription The short description of the AddOn. - * @param description The description of the AddOn. - * @param authors The authors of the AddOn. - * @param createEvents Whether to create example events. - * @param createSlashCommands Whether to create example slash commands. - * @param createMTSProperties Whether to create example MTS properties. - * @param createUDFs Whether to create example UDFs. (not yet implemented). - * @param readme The readme of the AddOn. - * @param licenseText The license text of the AddOn. - */ -public record NewAddOn( - String name, - String version, - String namespace, - String gitURL, - String website, - String license, - String shortDescription, - String description, - List authors, - boolean createEvents, - boolean createSlashCommands, - boolean createMTSProperties, - boolean createUDFs, - String readme, - String licenseText) {} diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java deleted file mode 100644 index 4b8257970c..0000000000 --- a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnBuilder.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.ui.addon.creator; - -import java.util.ArrayList; -import java.util.List; -import net.rptools.maptool.language.I18N; - -/** Builder for creating a {@link NewAddOn}. */ -public class NewAddOnBuilder { - - /** The name of the add-on. */ - private String name; - - /** The version of the add-on. */ - private String version; - - /** The namespace of the add-on. */ - private String namespace; - - /** The git URL of the add-on. */ - private String gitURL; - - /** The website of the add-on. */ - private String website; - - /** The license of the add-on. */ - private String license; - - /** The short description of the add-on. */ - private String shortDescription; - - /** The description of the add-on. */ - private String description; - - /** The authors of the add-on. */ - private final List authors = new ArrayList<>(); - - /** Whether to create events. */ - private boolean createEvents; - - /** Whether to create slash commands. */ - private boolean createSlashCommands; - - /** Whether to create MTS properties. */ - private boolean createMTSProperties; - - /** Whether to create UDFs. (not yet implemented) */ - private boolean createUDFs; - - /** - * Sets the name of the add-on. - * - * @param name the name of the add-on - * @return this builder. - */ - public NewAddOnBuilder setName(String name) { - this.name = name; - return this; - } - - /** - * Sets the version of the add-on. - * - * @param version the version of the add-on - * @return this builder. - */ - public NewAddOnBuilder setVersion(String version) { - this.version = version; - return this; - } - - /** - * Sets the namespace of the add-on. - * - * @param namespace the namespace of the add-on - * @return this builder. - */ - public NewAddOnBuilder setNamespace(String namespace) { - this.namespace = namespace; - return this; - } - - /** - * Sets the git URL of the add-on. - * - * @param gitURL the git URL of the add-on - * @return this builder. - */ - public NewAddOnBuilder setGitURL(String gitURL) { - this.gitURL = gitURL; - return this; - } - - /** - * Sets the website of the add-on. - * - * @param website the website of the add-on - * @return this builder. - */ - public NewAddOnBuilder setWebsite(String website) { - this.website = website; - return this; - } - - /** - * Sets the license of the add-on. - * - * @param license the license of the add-on - * @return this builder. - */ - public NewAddOnBuilder setLicense(String license) { - this.license = license; - return this; - } - - /** - * Sets the short description of the add-on. - * - * @param shortDescription the short description of the add-on - * @return this builder. - */ - public NewAddOnBuilder setShortDescription(String shortDescription) { - this.shortDescription = shortDescription; - return this; - } - - /** - * Sets the description of the add-on. - * - * @param description the description of the add-on - * @return this builder. - */ - public NewAddOnBuilder setDescription(String description) { - this.description = description; - return this; - } - - /** - * Sets the authors of the add-on. - * - * @param authors the authors of the add-on - * @return this builder. - */ - public NewAddOnBuilder setAuthors(List authors) { - this.authors.clear(); - this.authors.addAll(authors); - return this; - } - - /** - * Sets whether to create events. - * - * @param events whether to create events. - * @return this builder. - */ - public NewAddOnBuilder setCreateEvents(boolean events) { - this.createEvents = events; - return this; - } - - /** - * Sets whether to create slash commands. - * - * @param slashCommands whether to create slash commands. - * @return this builder. - */ - public NewAddOnBuilder setCreateSlashCommands(boolean slashCommands) { - this.createSlashCommands = slashCommands; - return this; - } - - /** - * Sets whether to create MTS properties. - * - * @param mtsProperties whether to create MTS properties. - * @return this builder. - */ - public NewAddOnBuilder setCreateMTSProperties(boolean mtsProperties) { - this.createMTSProperties = mtsProperties; - return this; - } - - /** - * Sets whether to create UDFs. - * - * @param udfs whether to create UDFs. - * @return this builder. - */ - public NewAddOnBuilder setCreateUDFs(boolean udfs) { - this.createUDFs = udfs; - return this; - } - - /** - * Builds a new {@link NewAddOn} from the current state of the builder. - * - * @return a new {@link NewAddOn}. - */ - public NewAddOn build() { - return new NewAddOn( - name, - version, - namespace, - gitURL, - website, - license, - shortDescription, - description, - authors, - createEvents, - createSlashCommands, - createMTSProperties, - createUDFs, - getReadMe(), - getLicenseText()); - } - - /** - * Gets the default README text. - * - * @return the default README text. - */ - private String getReadMe() { - return I18N.getText("library.dialog.addon.create.readeMe"); - } - - /** - * Gets the default license text. - * - * @return the default license text. - */ - private String getLicenseText() { - return I18N.getText("library.dialog.addon.create.licenseText"); - } -} diff --git a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java b/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java deleted file mode 100644 index 25054c2955..0000000000 --- a/src/main/java/net/rptools/maptool/client/ui/addon/creator/NewAddOnCreator.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.ui.addon.creator; - -import com.google.protobuf.util.JsonFormat; -import java.io.FileNotFoundException; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Path; -import net.rptools.maptool.language.I18N; -import net.rptools.maptool.model.library.addon.AddOnLibrary; -import net.rptools.maptool.model.library.addon.AddOnLibraryImporter; -import net.rptools.maptool.model.library.proto.AddOnLibraryDto; -import net.rptools.maptool.model.library.proto.AddOnLibraryEventsDto; -import net.rptools.maptool.model.library.proto.AddonSlashCommandsDto; -import net.rptools.maptool.model.library.proto.AddonSlashCommandsDto.AddOnSlashCommand; -import net.rptools.maptool.model.library.proto.MTScriptPropertiesDto; - -/** Creates a new add-on directory structure and files. */ -public class NewAddOnCreator { - - /** The name of the README file. */ - private static final String README_FILE = "README.md"; - - /** The name of the LICENSE file. */ - private static final String LICENSE_FILE = "license.txt"; - - /** The add-on to create. */ - private final NewAddOn addOn; - - /** The path to the add-on directory. */ - private final Path addOnPath; - - /** The path to the library directory. */ - private final Path libraryPath; - - /** The path to the mtscript directory. */ - private final Path mtscriptPath; - - /** The path to the public directory. */ - private final Path publicPath; - - /** The path to the mtscript public directory. */ - private final Path mtscriptPublicPath; - - private static final String AUTO_EXEC_EXAMPLE_MACRO = "public/auto_exec.mts"; - private static final String NON_AUTO_EXEC_EXAMPLE_MACRO = "public/non_auto_exec.mts"; - private static final String SLASH_COMMAND_EXAMPLE_MACRO = "testSlash.mts"; - private static final String ON_FIRST_INIT_EXAMPLE_MACRO = "onFirstInit.mts"; - private static final String ON_INIT_EXAMPLE_MACRO = "onInit.mts"; - private static final String ON_INITIATIVE_CHANGE_REQUEST_EXAMPLE_MACRO = - "onInitiativeChangeRequest.mts"; - private static final String ON_INITIATIVE_CHANGE_EXAMPLE_MACRO = "onInitiativeChange.mts"; - private static final String ON_TOKEN_MOVE_EXAMPLE_MACRO = "onTokenMove.mts"; - private static final String ON_MULTIPLE_TOKENS_MOVE_EXAMPLE_MACRO = "onMultipleTokensMove.mts"; - - /** - * Creates a new add-on creator. - * - * @param newAddOn the add-on to create. - * @param path the path to the add-on directory which will be created. - */ - public NewAddOnCreator(NewAddOn newAddOn, Path path) { - addOn = newAddOn; - addOnPath = path; - libraryPath = path.resolve(AddOnLibraryImporter.CONTENT_DIRECTORY); - mtscriptPath = libraryPath.resolve(AddOnLibrary.MTSCRIPT_DIR); - publicPath = libraryPath.resolve(AddOnLibrary.URL_PUBLIC_DIR); - mtscriptPublicPath = mtscriptPath.resolve(AddOnLibrary.MTSCRIPT_PUBLIC_DIR); - } - - /** - * Creates the add-on directory structure and files. - * - * @throws IOException if an error occurs creating the directory or files. - */ - public void create() throws IOException { - createAddOnDirectories(); - createLibraryFile(); - - if (addOn.createEvents()) { - createEvents(); - } - - if (addOn.createSlashCommands()) { - createSlashCommands(); - } - if (addOn.createMTSProperties()) { - createMTSProperties(); - } - createReadmeFile(); - createLicenseFile(); - } - - /** - * Creates the README file. - * - * @throws IOException if an error occurs creating the file. - */ - private void createLicenseFile() throws IOException { - try { - var licensePath = addOnPath.resolve(LICENSE_FILE); - var writer = new FileWriter(licensePath.toFile()); - writer.write(addOn.licenseText()); - writer.close(); - } catch (IOException e) { - throw new IOException(I18N.getText("library.dialog.failedToCreateFile", LICENSE_FILE), e); - } - } - - /** - * Creates the README file. - * - * @throws IOException if an error occurs creating the file. - */ - private void createReadmeFile() throws IOException { - try { - var readmePath = addOnPath.resolve(README_FILE); - var writer = new FileWriter(readmePath.toFile()); - writer.write(addOn.readme()); - writer.close(); - } catch (IOException e) { - throw new IOException(I18N.getText("library.dialog.failedToCreateFile", README_FILE), e); - } - } - - /** - * Creates the MTS properties file. - * - * @throws IOException if an error occurs creating the file. - */ - private void createMTSProperties() throws IOException { - var builder = MTScriptPropertiesDto.newBuilder(); - var propertiesBuilderAutoExec = MTScriptPropertiesDto.Property.newBuilder(); - propertiesBuilderAutoExec.setFilename(AUTO_EXEC_EXAMPLE_MACRO); - propertiesBuilderAutoExec.setAutoExecute(true); - propertiesBuilderAutoExec.setDescription(I18N.getText("library.dialog.create.autoExecDesc")); - - var propertiesBuilderNoAutoExec = MTScriptPropertiesDto.Property.newBuilder(); - propertiesBuilderNoAutoExec.setFilename(NON_AUTO_EXEC_EXAMPLE_MACRO); - propertiesBuilderNoAutoExec.setAutoExecute(false); - propertiesBuilderNoAutoExec.setDescription( - I18N.getText("library.dialog.create.noAutoExecDesc")); - - builder.addProperties(propertiesBuilderAutoExec); - builder.addProperties(propertiesBuilderNoAutoExec); - - try { - var mtsPropertiesPath = addOnPath.resolve(AddOnLibraryImporter.MACROSCRIPT_PROPERTY_FILE); - var writer = new FileWriter(mtsPropertiesPath.toFile()); - writer.write(JsonFormat.printer().print(builder)); - writer.close(); - } catch (IOException e) { - throw new IOException( - I18N.getText( - "library.dialog.failedToCreateFile", AddOnLibraryImporter.MACROSCRIPT_PROPERTY_FILE), - e); - } - writeMacro(mtscriptPath.resolve(AUTO_EXEC_EXAMPLE_MACRO)); - writeMacro(mtscriptPath.resolve(NON_AUTO_EXEC_EXAMPLE_MACRO)); - } - - /** - * Creates the slash commands file. - * - * @throws IOException if an error occurs creating the file. - */ - private void createSlashCommands() throws IOException { - var builder = AddonSlashCommandsDto.newBuilder(); - var slashCommandBuilder = AddOnSlashCommand.newBuilder(); - slashCommandBuilder.setName("exampleSlash"); - slashCommandBuilder.setDescription(I18N.getText("library.dialog.create.exampleSlashCmdDesc")); - slashCommandBuilder.setCommand(SLASH_COMMAND_EXAMPLE_MACRO); - builder.addSlashCommands(slashCommandBuilder); - - try { - var slashCommandPath = addOnPath.resolve(AddOnLibraryImporter.SLASH_COMMAND_FILE); - var writer = new FileWriter(slashCommandPath.toFile()); - writer.write(JsonFormat.printer().print(builder)); - writer.close(); - } catch (IOException e) { - throw new IOException( - I18N.getText( - "library.dialog.failedToCreateFile", AddOnLibraryImporter.SLASH_COMMAND_FILE), - e); - } - - writeMacro(mtscriptPath.resolve(SLASH_COMMAND_EXAMPLE_MACRO)); - } - - /** - * Creates the events file. - * - * @throws IOException if an error occurs creating the file. - */ - private void createEvents() throws IOException { - // Add init events - var builder = AddOnLibraryEventsDto.newBuilder(); - var onFirstInitMTSBuilder = AddOnLibraryEventsDto.Events.newBuilder(); - onFirstInitMTSBuilder.setName("onFirstInit"); - onFirstInitMTSBuilder.setMts(ON_FIRST_INIT_EXAMPLE_MACRO); - builder.addEvents(onFirstInitMTSBuilder); - var onInitMTSBuilder = AddOnLibraryEventsDto.Events.newBuilder(); - onInitMTSBuilder.setName("onInit"); - onInitMTSBuilder.setMts(ON_INIT_EXAMPLE_MACRO); - builder.addEvents(onInitMTSBuilder); - - // Add legacy events - var legacyEventsBuilder = AddOnLibraryEventsDto.newBuilder(); - var onInitiativeChangeRequestBuilder = AddOnLibraryEventsDto.Events.newBuilder(); - onInitiativeChangeRequestBuilder.setName("onInitiativeChangeRequest"); - onInitiativeChangeRequestBuilder.setMts(ON_INITIATIVE_CHANGE_REQUEST_EXAMPLE_MACRO); - var onInitiativeChangeBuilder = AddOnLibraryEventsDto.Events.newBuilder(); - onInitiativeChangeBuilder.setName("onInitiativeChange"); - onInitiativeChangeBuilder.setMts(ON_INITIATIVE_CHANGE_EXAMPLE_MACRO); - legacyEventsBuilder.addLegacyEvents(onInitiativeChangeBuilder); - var onTokenMoveBuilder = AddOnLibraryEventsDto.Events.newBuilder(); - onTokenMoveBuilder.setName("onTokenMove"); - onTokenMoveBuilder.setMts(ON_TOKEN_MOVE_EXAMPLE_MACRO); - legacyEventsBuilder.addLegacyEvents(onTokenMoveBuilder); - var onMultipleTokensMoveBuilder = AddOnLibraryEventsDto.Events.newBuilder(); - onMultipleTokensMoveBuilder.setName("onMultipleTokensMove"); - onMultipleTokensMoveBuilder.setMts(ON_MULTIPLE_TOKENS_MOVE_EXAMPLE_MACRO); - legacyEventsBuilder.addLegacyEvents(onMultipleTokensMoveBuilder); - - // Add events to builder - builder.addAllEvents(legacyEventsBuilder.getLegacyEventsList()); - try { - var eventsPath = addOnPath.resolve(AddOnLibraryImporter.EVENT_PROPERTY_FILE); - var writer = new FileWriter(eventsPath.toFile()); - writer.write(JsonFormat.printer().print(builder)); - writer.close(); - } catch (IOException e) { - throw new IOException( - I18N.getText( - "library.dialog.failedToCreateFile", AddOnLibraryImporter.EVENT_PROPERTY_FILE), - e); - } - - writeMacro(mtscriptPath.resolve(ON_FIRST_INIT_EXAMPLE_MACRO)); - writeMacro(mtscriptPath.resolve(ON_INIT_EXAMPLE_MACRO)); - writeMacro(mtscriptPath.resolve(ON_INITIATIVE_CHANGE_REQUEST_EXAMPLE_MACRO)); - writeMacro(mtscriptPath.resolve(ON_INITIATIVE_CHANGE_EXAMPLE_MACRO)); - writeMacro(mtscriptPath.resolve(ON_TOKEN_MOVE_EXAMPLE_MACRO)); - writeMacro(mtscriptPath.resolve(ON_MULTIPLE_TOKENS_MOVE_EXAMPLE_MACRO)); - } - - /** - * Creates the library file. - * - * @throws IOException if an error occurs creating the file. - */ - private void createLibraryFile() throws IOException { - var builder = AddOnLibraryDto.newBuilder(); - builder.setName(addOn.name()); - builder.setVersion(addOn.version()); - builder.setNamespace(addOn.namespace()); - builder.addAllAuthors(addOn.authors()); - if (addOn.gitURL() != null && !addOn.gitURL().isEmpty()) { - builder.setGitUrl(addOn.gitURL()); - } - if (addOn.website() != null && !addOn.website().isEmpty()) { - builder.setWebsite(addOn.website()); - } - if (addOn.license() != null && !addOn.license().isEmpty()) { - builder.setLicense(addOn.license()); - } - builder.setShortDescription(addOn.shortDescription()); - builder.setDescription(addOn.description()); - builder.setLicenseFile(LICENSE_FILE); - builder.setReadMeFile(README_FILE); - try { - var libraryInfoPath = addOnPath.resolve(AddOnLibraryImporter.LIBRARY_INFO_FILE); - var writer = new FileWriter(libraryInfoPath.toFile()); - writer.write(JsonFormat.printer().includingDefaultValueFields().print(builder)); - writer.close(); - } catch (IOException e) { - throw new IOException( - I18N.getText("library.dialog.failedToCreateFile", AddOnLibraryImporter.LIBRARY_INFO_FILE), - e); - } - } - - /** - * Creates the add-on directories. - * - * @throws IOException if an error occurs creating the directories. - */ - private void createAddOnDirectories() throws IOException { - createAddOnDirectory(addOnPath); - createAddOnDirectory(libraryPath); - createAddOnDirectory(mtscriptPath); - createAddOnDirectory(mtscriptPublicPath); - createAddOnDirectory(publicPath); - } - - /** - * Creates a new directory. - * - * @param path the path to the directory to create. - * @throws IOException if an error occurs creating the directory.. - */ - private void createAddOnDirectory(Path path) throws IOException { - if (!path.toFile().mkdirs()) { - throw new IOException(I18N.getText("library.dialog.failedToCreateDir", path.toString())); - } - } - - /** - * Write an example macro to the given path. - * - * @param path the path to write the macro to (including filename) - */ - private void writeMacro(Path path) throws FileNotFoundException { - var filename = path.getFileName().toString(); - var comment = I18N.getText("library.dialog.addon.create.mtsComment", filename); - try (var writer = new PrintWriter(path.toFile())) { - writer.println(""); - writer.println("[h: broadcast('" + filename + "')] "); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java index a7e815c597..4f6aa5dd51 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialog.java @@ -454,9 +454,8 @@ private void initExportButton() { JOptionPane.INFORMATION_MESSAGE); CampaignPropertiesDto campaignPropertiesDto = MapTool.getCampaign().getCampaignProperties().toDto(); - try (FileOutputStream fos = new FileOutputStream(chooser.getSelectedFile())) { - fos.write(JsonFormat.printer().print(campaignPropertiesDto).getBytes()); - } + FileOutputStream fos = new FileOutputStream(chooser.getSelectedFile()); + fos.write(JsonFormat.printer().print(campaignPropertiesDto).getBytes()); } } catch (IOException ioe) { diff --git a/src/main/java/net/rptools/maptool/model/library/ExternalAddonsUpdateEvent.java b/src/main/java/net/rptools/maptool/model/library/ExternalAddonsUpdateEvent.java deleted file mode 100644 index f0cdfcbbfb..0000000000 --- a/src/main/java/net/rptools/maptool/model/library/ExternalAddonsUpdateEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.model.library; - -/** Event that is fired when the list of external addons is updated. */ -public record ExternalAddonsUpdateEvent() {} diff --git a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java index f9624e356d..f5507f78d3 100644 --- a/src/main/java/net/rptools/maptool/model/library/LibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/LibraryManager.java @@ -14,9 +14,7 @@ */ package net.rptools.maptool.model.library; -import java.io.IOException; import java.net.URL; -import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -28,7 +26,11 @@ import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.MapToolMacroContext; import net.rptools.maptool.events.MapToolEventBus; -import net.rptools.maptool.model.library.addon.*; +import net.rptools.maptool.model.library.addon.AddOnLibrary; +import net.rptools.maptool.model.library.addon.AddOnLibraryData; +import net.rptools.maptool.model.library.addon.AddOnLibraryManager; +import net.rptools.maptool.model.library.addon.AddOnSlashCommandManager; +import net.rptools.maptool.model.library.addon.TransferableAddOnLibrary; import net.rptools.maptool.model.library.builtin.BuiltInLibraryManager; import net.rptools.maptool.model.library.proto.AddOnLibraryListDto; import net.rptools.maptool.model.library.token.LibraryTokenManager; @@ -72,63 +74,12 @@ public class LibraryManager { private static final AddOnSlashCommandManager addOnSlashCommandManager = new AddOnSlashCommandManager(); - /** - * Initializes the library manager. This method should be called after instantiation of the - * library manager. - */ public static void init() { libraryTokenManager.init(); builtInLibraryManager.loadBuiltIns(); - addOnLibraryManager.init(); new MapToolEventBus().getMainEventBus().register(addOnSlashCommandManager); } - /** - * Returns the list of external add-on libraries. - * - * @return the list of external add-on libraries. - */ - public List getExternalAddOnLibraries() { - return addOnLibraryManager.getExternalAddOnLibraries(); - } - - /** - * Returns if external add-on libraries are enabled. - * - * @return if external add-on libraries are enabled. - */ - public boolean externalLibrariesEnabled() { - return addOnLibraryManager.externalLibrariesEnabled(); - } - - /** - * Sets if external add-on libraries are enabled. - * - * @param enabled if external add-on libraries are enabled. - */ - public void setExternalLibrariesEnabled(boolean enabled) throws IOException { - addOnLibraryManager.setExternalLibrariesEnabled(enabled); - } - - /** - * Returns the path to the external add-on libraries. - * - * @return the path to the external add-on libraries. - */ - public Path getEternalLibraryPath() { - return addOnLibraryManager.getExternalLibraryPath(); - } - - /** - * Sets the path to the external add-on libraries. - * - * @param path the path to the external add-on libraries. - * @throws IOException if an error occurs while setting the path. - */ - public void setExternalLibraryPath(Path path) throws IOException { - addOnLibraryManager.setExternalLibraryPath(path); - } - /** * Checks to see if this library name used a reserved prefix. * @@ -210,7 +161,7 @@ public boolean addOnLibraryExists(String namespace) { /** * Register and add-on library. * - * @param addOn the Add-On to register. + * @param addOn the Add On to register. */ public boolean registerAddOnLibrary(AddOnLibrary addOn) { try { @@ -226,8 +177,7 @@ public boolean registerAddOnLibrary(AddOnLibrary addOn) { } /** - * Deregister the add-on in library associated with the specified namespace. This will deregister - * the add-on if it is external. + * Deregister the add-on in library associated with the specified namespace. * * @param namespace the namespace to deregister. */ @@ -239,7 +189,7 @@ public void deregisterAddOnLibrary(String namespace) { } /** - * Register an add-on in library, replacing any existing library. + * Register a add-on in library, replacing any existing library. * * @param addOnLibrary the add-on in library to register. */ @@ -258,30 +208,6 @@ public boolean reregisterAddOnLibrary(AddOnLibrary addOnLibrary) { return true; } - /** - * Register an add-on as an external add-on. This will not make the add-on available to - * MapTool. To make the add-on available to MapTool, use {@link #importFromExternal(String)} - * - * @param path The path of the add-on to register. - * @throws IOException if an error occurs while registering the add-on. - */ - public void registerExternalAddOnLibrary(Path path) throws IOException { - addOnLibraryManager.registerExternalLibrary(path); - } - - /** - * Import an add-on from an external source. This will make the add-on available to MapTool. - * Importing an updated version of an add-on will replace the existing add-on. - * - * @param namespace The namespace of the add-on to import. - */ - public void importFromExternal(String namespace) throws IOException { - if (addOnLibraryManager.namespaceRegistered(namespace)) { - addOnLibraryManager.deregisterLibrary(namespace); - } - addOnLibraryManager.importFromExternal(namespace); - } - /** * Returns a list of information about the registered libraries. * @@ -415,7 +341,6 @@ public void removeAddOnLibrary(String namespace) { * initialization next time they are added. */ public void removeAddOnLibraries() { - for (var lib : addOnLibraryManager.getLibraries()) { lib.getLibraryData() .thenAccept( diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java index 9d2264c417..a24ad46892 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibrary.java @@ -19,7 +19,6 @@ import java.io.InputStream; import java.net.URI; import java.net.URL; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -68,10 +67,10 @@ public class AddOnLibrary implements Library { /** The name of the event for first time initialization. */ - public static final String FIRST_INIT_EVENT = "onFirstInit"; + private static final String FIRST_INIT_EVENT = "onFirstInit"; /** The name of the event for initialization. */ - public static final String INIT_EVENT = "onInit"; + private static final String INIT_EVENT = "onInit"; /** The prefix for the name of the JavaScript context for this addon. */ private static final String JS_CONTEXT_PREFIX = "addon:"; @@ -80,13 +79,13 @@ public class AddOnLibrary implements Library { private record MTScript(String path, boolean autoExecute, String description, MD5Key md5Key) {} /** The directory where the files exposed URI are stored. */ - public static final String URL_PUBLIC_DIR = "public/"; + private static final String URL_PUBLIC_DIR = "public/"; /** The directory where MT MacroScripts are stored. */ - public static final String MTSCRIPT_DIR = "mtscript/"; + private static final String MTSCRIPT_DIR = "mtscript/"; /** The directory where public MT MacroScripts are stored. */ - public static final String MTSCRIPT_PUBLIC_DIR = "public/"; + private static final String MTSCRIPT_PUBLIC_DIR = "public/"; /** Logger instance for this class. */ private static final Logger logger = LogManager.getLogger(AddOnLibrary.class); @@ -160,12 +159,6 @@ private record MTScript(String path, boolean autoExecute, String description, MD /** The information about the add-on library. */ private final LibraryInfo libraryInfo; - /** - * The directory that the library is in for development mode, or null if the add-on is not in - * development mode. - */ - private final Path backingDirectory; - /** * Class used to represent Drop In Libraries. * @@ -174,8 +167,6 @@ private record MTScript(String path, boolean autoExecute, String description, MD * @param eventsDto The MTScript Events Data Transfer Object. * @param slashCommandsDto The Slash Commands Data Transfer Object. * @param pathAssetMap mapping of paths in the library to {@link MD5Key}s and {@link Type}s. - * @param backingDirectory The directory that the library is in for development mode, or null if - * the add-on is not in development mode. */ private AddOnLibrary( MD5Key libraryAssetKey, @@ -184,8 +175,7 @@ private AddOnLibrary( AddOnLibraryEventsDto eventsDto, AddOnStatSheetsDto statSheetsDto, AddonSlashCommandsDto slashCommandsDto, - Map> pathAssetMap, - Path backingDirectory) { + Map> pathAssetMap) { Objects.requireNonNull(dto, I18N.getText("library.error.invalidDefinition")); name = Objects.requireNonNull(dto.getName(), I18N.getText("library.error.emptyName")); version = @@ -268,8 +258,6 @@ private AddOnLibrary( jsContextName = JS_CONTEXT_PREFIX + namespace; - this.backingDirectory = backingDirectory; - libraryInfo = new LibraryInfo( name, @@ -319,47 +307,7 @@ public static AddOnLibrary fromDto( Map> pathAssetMap) { return new AddOnLibrary( - libraryAssetKey, - dto, - mtsDto, - eventsDto, - statSheetsDto, - slashCommandsDto, - pathAssetMap, - null); - } - - /** - * Creates a new Drop In Library from the given {@link AddOnLibraryDto}, {@link - * MTScriptPropertiesDto}, and file path assets map. - * - * @param dto The Drop In Libraries Data Transfer Object. - * @param mtsDto The MTScript Properties Data Transfer Object. - * @param eventsDto The Events Data Transfer Object. - * @param slashCommandsDto The Slash Commands Data Transfer Object. - * @param pathAssetMap mapping of paths in the library to {@link MD5Key}s and {@link Asset.Type}s. - * @param backingDirectory The directory that the library is in for development mode, or null if - * the add-on is not in development mode. - * @return the new Add on library. - */ - public static AddOnLibrary fromDto( - MD5Key libraryAssetKey, - AddOnLibraryDto dto, - MTScriptPropertiesDto mtsDto, - AddOnLibraryEventsDto eventsDto, - AddOnStatSheetsDto statSheetsDto, - AddonSlashCommandsDto slashCommandsDto, - Map> pathAssetMap, - Path backingDirectory) { - return new AddOnLibrary( - libraryAssetKey, - dto, - mtsDto, - eventsDto, - statSheetsDto, - slashCommandsDto, - pathAssetMap, - backingDirectory); + libraryAssetKey, dto, mtsDto, eventsDto, statSheetsDto, slashCommandsDto, pathAssetMap); } @Override @@ -729,12 +677,6 @@ private void runJS(String file) { .join(); } - /** - * Returns the DataValue for the specified path in the add-on library. - * - * @param path the path to the file to read. - * @return the DataValue for the specified path in the add-on library. - */ CompletableFuture readFile(String path) { return CompletableFuture.supplyAsync( () -> { @@ -747,24 +689,4 @@ CompletableFuture readFile(String path) { return DataValueFactory.fromAsset(filePath, asset); }); } - - /** - * Returns if the add-on library is in development mode or not. - * - * @return true if the add-on library is in development mode, false - * otherwise. - */ - public boolean isInDevelopmentMode() { - return backingDirectory != null; - } - - /** - * Returns the directory that the library is in for development mode, or null if the add-on is not - * in development mode. - * - * @return the directory for the add-on. - */ - public Path getBackingDirectory() { - return backingDirectory; - } } diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java index 1eb0d30626..e0b66d3cb4 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryImporter.java @@ -21,27 +21,21 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.util.HashMap; import java.util.Map; -import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; import javax.swing.filechooser.FileFilter; import net.rptools.lib.MD5Key; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.Asset; import net.rptools.maptool.model.Asset.Type; import net.rptools.maptool.model.AssetManager; -import net.rptools.maptool.model.library.LibraryInfo; import net.rptools.maptool.model.library.proto.AddOnLibraryDto; import net.rptools.maptool.model.library.proto.AddOnLibraryEventsDto; import net.rptools.maptool.model.library.proto.AddOnStatSheetsDto; import net.rptools.maptool.model.library.proto.AddonSlashCommandsDto; import net.rptools.maptool.model.library.proto.MTScriptPropertiesDto; -import net.rptools.maptool.util.FileUtil; import org.apache.tika.mime.MediaType; import org.javatuples.Pair; @@ -141,128 +135,6 @@ public AddOnLibrary importFromAsset(Asset asset) throws IOException { return importFromFile(tempFile); } - /** - * Imports the {@link LibraryInfo} from the specified directory. If the {@code LIBRARY_INFO_FILE} - * does not exist in this directory then null will be returned. - * - * @param dir The directory to import the library info from. - * @return the {@link LibraryInfo} that was imported, {@code null} if the library info file does - * not exist. - * @throws IOException if an error occurs while reading the library. - */ - public LibraryInfo getLibraryInfoFromDirectory(Path dir) throws IOException { - var infoPath = dir.resolve(LIBRARY_INFO_FILE).toAbsolutePath(); - if (!Files.exists(infoPath)) { - return null; - } - - var builder = AddOnLibraryDto.newBuilder(); - JsonFormat.parser().ignoringUnknownFields().merge(Files.newBufferedReader(infoPath), builder); - - return new LibraryInfo( - builder.getName(), - builder.getNamespace(), - builder.getVersion(), - builder.getWebsite(), - builder.getGitUrl(), - builder.getAuthorsList().toArray(new String[0]), - builder.getLicense(), - builder.getDescription(), - builder.getShortDescription(), - builder.getAllowsUriAccess(), - builder.getReadMeFile(), - builder.getLicenseFile()); - } - - /** - * Imports the add-on library from the specified directory - * - * @param dir the directory to import the library from. - * @return the {@link AddOnLibrary} that was imported. - * @throws IOException if an error occurs while reading the library. - */ - public AddOnLibrary importFromDirectory(Path dir) throws IOException { - - var infoPath = dir.resolve(LIBRARY_INFO_FILE); - if (!Files.exists(infoPath)) { - throw new IOException(I18N.getText("library.error.addOn.noConfigFile", dir)); - } - - var builder = AddOnLibraryDto.newBuilder(); - JsonFormat.parser().ignoringUnknownFields().merge(Files.newBufferedReader(infoPath), builder); - - var pathAssetMap = processAssetsFromDirectory(builder.getNamespace(), dir); - - var mtsPropBuilder = MTScriptPropertiesDto.newBuilder(); - var mtsPropPath = dir.resolve(MACROSCRIPT_PROPERTY_FILE); - if (Files.exists(mtsPropPath)) - JsonFormat.parser() - .ignoringUnknownFields() - .merge(Files.newBufferedReader(mtsPropPath), mtsPropBuilder); - - var eventPropBuilder = AddOnLibraryEventsDto.newBuilder(); - var eventPropPath = dir.resolve(EVENT_PROPERTY_FILE); - if (Files.exists(eventPropPath)) - JsonFormat.parser() - .ignoringUnknownFields() - .merge(Files.newBufferedReader(eventPropPath), eventPropBuilder); - - var statSheetsBuilder = AddOnStatSheetsDto.newBuilder(); - var statSheetsPath = dir.resolve(STATS_SHEET_FILE); - if (Files.exists(statSheetsPath)) - JsonFormat.parser() - .ignoringUnknownFields() - .merge(Files.newBufferedReader(statSheetsPath), statSheetsBuilder); - - var slashCommandsBuilder = AddonSlashCommandsDto.newBuilder(); - var slashCommandsPath = dir.resolve(SLASH_COMMAND_FILE); - if (Files.exists(slashCommandsPath)) - JsonFormat.parser() - .ignoringUnknownFields() - .merge(Files.newBufferedReader(slashCommandsPath), slashCommandsBuilder); - - addMetaDataFromDirectory(builder.getNamespace(), dir, pathAssetMap); - - // Directory assets must be zipped up. When external add-on libraries are sent to remotes, - // they should act as normal add-on libraries. - var addOnLib = builder.build(); - - var zipPath = Files.createTempFile(builder.getNamespace(), null); - try (var zipOut = - new ZipOutputStream(Files.newOutputStream(zipPath, StandardOpenOption.WRITE))) { - var paths = - pathAssetMap.keySet().stream() - .map( - path -> { - if (path.startsWith(METADATA_DIR)) return path.substring(METADATA_DIR.length()); - return CONTENT_DIRECTORY + path; - }) - .collect(Collectors.toSet()); - - for (var pathString : paths) { - zipOut.putNextEntry(new ZipEntry(pathString)); - var p = dir.resolve(pathString); - if (Files.isRegularFile(p)) zipOut.write(Files.readAllBytes(p)); - zipOut.closeEntry(); - } - } - - var data = Files.readAllBytes(zipPath); - Files.delete(zipPath); - - var asset = Type.MTLIB.getFactory().apply(addOnLib.getNamespace(), data); - addAsset(asset); - - return AddOnLibrary.fromDto( - asset.getMD5Key(), - addOnLib, - mtsPropBuilder.build(), - eventPropBuilder.build(), - statSheetsBuilder.build(), - slashCommandsBuilder.build(), - pathAssetMap); - } - /** * Imports the add-on library from the specified file. * @@ -271,6 +143,7 @@ public AddOnLibrary importFromDirectory(Path dir) throws IOException { * @throws IOException if an error occurs while reading the asset. */ public AddOnLibrary importFromFile(File file) throws IOException { + var diiBuilder = AddOnLibraryDto.newBuilder(); try (var zip = new ZipFile(file)) { ZipEntry entry = zip.getEntry(LIBRARY_INFO_FILE); @@ -283,7 +156,7 @@ public AddOnLibrary importFromFile(File file) throws IOException { .merge(new InputStreamReader(zip.getInputStream(entry)), builder); // MT MacroScript properties - var pathAssetMap = processAssetsFromZip(builder.getNamespace(), zip); + var pathAssetMap = processAssets(builder.getNamespace(), zip); var mtsPropBuilder = MTScriptPropertiesDto.newBuilder(); ZipEntry mtsPropsZipEntry = zip.getEntry(MACROSCRIPT_PROPERTY_FILE); if (mtsPropsZipEntry != null) { @@ -322,7 +195,7 @@ public AddOnLibrary importFromFile(File file) throws IOException { } // Copy Metadata - addMetaDataFromZip(builder.getNamespace(), zip, pathAssetMap); + addMetaData(builder.getNamespace(), zip, pathAssetMap); var addOnLib = builder.build(); byte[] data = Files.readAllBytes(file.toPath()); @@ -367,7 +240,7 @@ public AddOnLibrary importFromClassPath(String path) throws IOException { * @param pathAssetMap the map of asset paths and asset details. * @throws IOException */ - private void addMetaDataFromZip( + private void addMetaData( String namespace, ZipFile zip, Map> pathAssetMap) throws IOException { var entries = zip.stream().filter(e -> !e.getName().contains("/")).toList(); @@ -384,29 +257,6 @@ private void addMetaDataFromZip( } } - /** - * Adds the metadata from the add-on directory to the metadata directory. - * - * @param namespace The namespace of the add-on. - * @param dir The directory of the add-on. - * @param pathAssetMap The asset details output. - * @throws IOException If there is an error reading assets from the directory. - */ - private void addMetaDataFromDirectory( - String namespace, Path dir, Map> pathAssetMap) throws IOException { - var entries = Files.list(dir).filter(p -> !Files.isDirectory(p)).collect(Collectors.toSet()); - for (var entry : entries) { - var path = METADATA_DIR + entry.getFileName().toString(); - var bytes = Files.readAllBytes(entry); - - var mediaType = Asset.getMediaType(entry.getFileName().toString(), bytes); - - var asset = Type.fromMediaType(mediaType).getFactory().apply(namespace + "/" + path, bytes); - addAsset(asset); - pathAssetMap.put(path, Pair.with(asset.getMD5Key(), asset.getType())); - } - } - /** * Reads the assets from the add-on library and adds them to the asset manager. * @@ -415,7 +265,7 @@ private void addMetaDataFromDirectory( * @return a map of asset paths and asset details. * @throws IOException if there is an error reading the assets from the add-on library. */ - private Map> processAssetsFromZip(String namespace, ZipFile zip) + private Map> processAssets(String namespace, ZipFile zip) throws IOException { var pathAssetMap = new HashMap>(); var entries = @@ -437,39 +287,6 @@ private Map> processAssetsFromZip(String namespace, Z return pathAssetMap; } - /** - * Reads the assets from a flat directory. This is primarily used for external libraries, such as - * development-mode libraries. - * - * @param namespace The namespace to classify assets under. - * @param dir The directory to process as an add-on. - * @return A map containing asset paths and details. - * @throws IOException If there is an error reading assets from the directory. - */ - private Map> processAssetsFromDirectory(String namespace, Path dir) - throws IOException { - var pathAssetMap = new HashMap>(); - var contentDir = dir.resolve(CONTENT_DIRECTORY); - - // Empty libraries are still permitted. - if (!Files.exists(contentDir)) return pathAssetMap; - - for (Path entry : FileUtil.listRecursively(contentDir).collect(Collectors.toSet())) { - if (Files.isDirectory(entry)) continue; - entry = dir.relativize(entry); - var pathString = entry.toString().substring(CONTENT_DIRECTORY.length()).replace('\\', '/'); - var bytes = Files.readAllBytes(dir.resolve(entry)); - - var mediaType = Asset.getMediaType(entry.toString(), bytes); - - var asset = - Type.fromMediaType(mediaType).getFactory().apply(namespace + "/" + pathString, bytes); - addAsset(asset); - pathAssetMap.put(pathString, Pair.with(asset.getMD5Key(), asset.getType())); - } - return pathAssetMap; - } - /** * Adds the {@link Asset} to the {@link AssetManager} if it does not already exist. * diff --git a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java index f233743dc2..5d04309356 100644 --- a/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java +++ b/src/main/java/net/rptools/maptool/model/library/addon/AddOnLibraryManager.java @@ -14,9 +14,7 @@ */ package net.rptools.maptool.model.library.addon; -import java.io.IOException; import java.net.URL; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -25,8 +23,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import net.rptools.maptool.client.AppPreferences; -import net.rptools.maptool.client.MapTool; import net.rptools.maptool.events.MapToolEventBus; import net.rptools.maptool.model.library.AddOnsAddedEvent; import net.rptools.maptool.model.library.AddOnsRemovedEvent; @@ -44,9 +40,6 @@ public class AddOnLibraryManager { /** The add-on libraries that are registered. */ private final Map namespaceLibraryMap = new ConcurrentHashMap<>(); - /** The external add-on library manager. */ - private ExternalAddOnLibraryManager externalAddOnLibraryManager; - /** * Is there a add-on library that would handle this path. This just checks the protocol and * namespace, it won't check that the full path actually exists. @@ -92,16 +85,6 @@ public void registerLibrary(AddOnLibrary library) { .post(new AddOnsAddedEvent(Set.of(library.getLibraryInfo().join()))); } - /** - * Checks to see if the specified namespace is registered. - * - * @param namespace the namespace to check. - * @return {@code true} if the namespace is registered. - */ - public boolean isNamespaceRegistered(String namespace) { - return namespaceLibraryMap.containsKey(namespace.toLowerCase()); - } - /** * Deregister the add-on library with the specified namespace. * @@ -188,7 +171,7 @@ public void removeAllLibraries() { .map(CompletableFuture::join) .collect(Collectors.toSet()); - if (!libs.isEmpty()) { + if (libs.size() > 0) { new MapToolEventBus().getMainEventBus().post(new AddOnsRemovedEvent(libs)); for (var library : namespaceLibraryMap.values()) { library.cleanup(); @@ -210,105 +193,4 @@ public CompletableFuture> getLegacyEventTargets(String eventName) { .filter(l -> l.getLegacyEvents().contains(eventName)) .collect(Collectors.toSet())); } - - /** Initializes the add-on library manager. */ - public void init() { - externalAddOnLibraryManager = new ExternalAddOnLibraryManager(this); - String path = AppPreferences.externalAddOnLibrariesPath.get(); - - try { - externalAddOnLibraryManager.setExternalLibraryPath(Path.of(path)); - externalAddOnLibraryManager.setEnabled(AppPreferences.externalAddOnLibrariesEnabled.get()); - externalAddOnLibraryManager.init(); - } catch (IOException e) { - MapTool.showError("Error setting external library path", e); - try { - externalAddOnLibraryManager.setEnabled(false); - } catch (IOException ex) { - // Do nothing as it shouldn't happen, but if it does there is no action we can take - } - } - } - - /** - * Replaces the add-on library with a newer version. - * - * @param library the library to replace the existing library with. - */ - public void replaceLibrary(AddOnLibrary library) { - library - .getNamespace() - .thenAccept( - namespace -> { - deregisterLibrary(namespace); - registerLibrary(library); - }); - } - - /** - * Returns the information of external add-on libraries that are registered. - * - * @return the information of external add-on libraries that are registered. - */ - public List getExternalAddOnLibraries() { - return externalAddOnLibraryManager.getLibraries(); - } - - /** - * Returns if external add-on libraries are enabled. - * - * @return if external add-on libraries are enabled. - */ - public boolean externalLibrariesEnabled() { - return externalAddOnLibraryManager.isEnabled(); - } - - /** - * Sets if external add-on libraries are enabled. - * - * @param enabled if external add-on libraries are enabled. - * @throws IOException if an I/O error occurs. - */ - public void setExternalLibrariesEnabled(boolean enabled) throws IOException { - externalAddOnLibraryManager.setEnabled(enabled); - } - - /** - * Returns the path to the external add-on libraries. - * - * @return the path to the external add-on libraries. - */ - public Path getExternalLibraryPath() { - return externalAddOnLibraryManager.getExternalLibraryPath(); - } - - /** - * Sets the path to the external add-on libraries. - * - * @param path the path to the external add-on libraries. - * @throws IOException if an I/O error occurs. - */ - public void setExternalLibraryPath(Path path) throws IOException { - externalAddOnLibraryManager.setExternalLibraryPath(path); - } - - /** - * Registers the add-on library as an external library. - * - * @param path The path to the library. - * @throws IOException if an I/O error occurs. - */ - public void registerExternalLibrary(Path path) throws IOException { - externalAddOnLibraryManager.registerExternalAddOnLibrary(path); - } - - /** - * Makes the external library with the given namespace available to MapTool. Importing an existing - * library will replace the existing library. - * - * @param namespace The namespace of the library. - */ - public void importFromExternal(String namespace) throws IOException { - externalAddOnLibraryManager.importLibrary(namespace); - } } diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java deleted file mode 100644 index 93804d6bb6..0000000000 --- a/src/main/java/net/rptools/maptool/model/library/addon/ExternalAddOnLibraryManager.java +++ /dev/null @@ -1,435 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.model.library.addon; - -import com.google.common.eventbus.Subscribe; -import io.methvin.watcher.DirectoryWatcher; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReentrantLock; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.events.MapToolEventBus; -import net.rptools.maptool.language.I18N; -import net.rptools.maptool.model.library.AddOnsAddedEvent; -import net.rptools.maptool.model.library.AddOnsRemovedEvent; -import net.rptools.maptool.model.library.ExternalAddonsUpdateEvent; -import net.rptools.maptool.model.library.LibraryInfo; - -/** - * Manages the external add-on libraries that are baked by the file system. This manager will watch - * the external add-on library directory for changes and will update the add-on libraries available - * but will not automatically update the add-on libraries that MapTool has loaded. - */ -public class ExternalAddOnLibraryManager { - - /** The add-on library manager that is used to register the external add-on libraries. */ - private final AddOnLibraryManager addOnLibraryManager; - - /** The add-on libraries that are registered. */ - private final Map namespaceInfoMap = new ConcurrentHashMap<>(); - - /** Is the external add-on library manager enabled. */ - private boolean enabled = false; - - /** The path to the external add-on libraries. */ - private Path externalLibraryPath = null; - - /** Is the external add-on library manager initialised. */ - private boolean initialised = false; - - /** Directory watcher for watching the external add-on library directory. */ - private DirectoryWatcher directoryWatcher; - - /** Lock for managing enabled and path states. */ - private final ReentrantLock lock = new ReentrantLock(); - - /** - * Creates a new instance of the external add-on library manager. {@code init()} must be called - * after construction. - * - * @param addOnLibraryManager the add-on library manager used to register the add-on libraries. - */ - public ExternalAddOnLibraryManager(AddOnLibraryManager addOnLibraryManager) { - this.addOnLibraryManager = addOnLibraryManager; - } - - /** - * Initializes the external add-on library manager. It is safe to call {@see - * setExternalLibraryPath(Path)} and {@see setEnabled(boolean)} before calling this method, but no - * directory watching will occur until this method is called. - * - * @throws IOException if an error occurs. - * @throws IllegalStateException if the external add-on library manager has already been - * initialised. - */ - public void init() throws IOException { - try { - lock.lock(); - if (initialised) { - throw new IllegalStateException("External add-on library manager already initialised"); - } - initialised = true; - startWatching(); - } finally { - lock.unlock(); - } - var eventBus = new MapToolEventBus().getMainEventBus(); - eventBus.register(this); - } - - /** - * Handles the event when an add-on library is added to MapTool. - * - * @param event the add-on library that was added to MapTool. - */ - @Subscribe - public void onLibraryAdded(AddOnsAddedEvent event) { - boolean updated = false; - for (LibraryInfo libraryInfo : event.addOns()) { - var namespace = libraryInfo.namespace().toLowerCase(); - if (namespaceInfoMap.containsKey(namespace)) { - var oldInfo = namespaceInfoMap.get(namespace); - var newInfo = - new ExternalLibraryInfo( - namespace, - oldInfo.libraryInfo(), - false, - true, - oldInfo.backingDirectory(), - externalLibraryPath.relativize(oldInfo.backingDirectory()).toString()); - namespaceInfoMap.put(namespace, newInfo); - updated = true; - } - } - if (updated) { - new MapToolEventBus().getMainEventBus().post(new ExternalAddonsUpdateEvent()); - } - } - - /** - * Handles the event when an add-on library is removed from MapTool. - * - * @param event the add-on library that was removed from MapTool. - */ - @Subscribe - public void onLibraryRemoved(AddOnsRemovedEvent event) { - boolean updated = false; - for (LibraryInfo libraryInfo : event.addOns()) { - var namespace = libraryInfo.namespace().toLowerCase(); - if (namespaceInfoMap.containsKey(namespace)) { - var oldInfo = namespaceInfoMap.get(namespace); - var newInfo = - new ExternalLibraryInfo( - namespace, - oldInfo.libraryInfo(), - true, - false, - oldInfo.backingDirectory(), - externalLibraryPath.relativize(oldInfo.backingDirectory()).toString()); - namespaceInfoMap.put(namespace, newInfo); - updated = true; - } - } - if (updated) { - new MapToolEventBus().getMainEventBus().post(new ExternalAddonsUpdateEvent()); - } - } - - /** - * Registers an external add-on library. - * - * @param info Information about the add-on library to register. - */ - private void registerExternalAddOnLibrary(ExternalLibraryInfo info) { - boolean isInstalled = addOnLibraryManager.isNamespaceRegistered(info.namespace()); - var externalInfo = - new ExternalLibraryInfo( - info.namespace(), - info.libraryInfo(), - true, - isInstalled, - info.backingDirectory(), - externalLibraryPath.relativize(info.backingDirectory()).toString()); - namespaceInfoMap.put(info.namespace().toLowerCase(), externalInfo); - } - - /** - * Deregisters an external add-on library. - * - * @param path the backing path of the add-on library to deregister. - */ - public void deregisterExternalAddOnLibrary(Path path) { - namespaceInfoMap.values().stream() - .filter(info -> info.backingDirectory().equals(path)) - .findFirst() - .ifPresent(info -> namespaceInfoMap.remove(info.namespace().toLowerCase())); - var eventBus = new MapToolEventBus().getMainEventBus(); - eventBus.post(new ExternalAddonsUpdateEvent()); - } - - /** - * Refreshes an external add-on library. - * - * @param path the path to the add-on library. - * @throws IOException if an error occurs. - */ - public void refreshExternalAddOnLibrary(Path path) throws IOException { - registerExternalAddOnLibrary(path); // Allows us to change behaviour later without breaking API - } - - /** - * Registers an external add-on library. - * - * @param path the path to the add-on library. - * @throws IOException if an error occurs. - */ - public void registerExternalAddOnLibrary(Path path) throws IOException { - var lib = new AddOnLibraryImporter().getLibraryInfoFromDirectory(path); - if (lib == null) { - return; - } - boolean isInstalled = addOnLibraryManager.isNamespaceRegistered(lib.namespace()); - var info = - new ExternalLibraryInfo( - lib.namespace(), - lib, - false, - isInstalled, - path, - externalLibraryPath.relativize(path).toString()); - registerExternalAddOnLibrary(info); - var eventBus = new MapToolEventBus().getMainEventBus(); - eventBus.post(new ExternalAddonsUpdateEvent()); - } - - /** - * Gets the external libraries that have been registered. - * - * @return the external libraries. - */ - public List getLibraries() { - return new ArrayList<>(namespaceInfoMap.values()); - } - - /** - * Is the external add-on library manager enabled. - * - * @return {@code true} if the external add-on library manager is enabled. - */ - public boolean isEnabled() { - try { - lock.lock(); - return enabled; - } finally { - lock.unlock(); - } - } - - /** - * Sets the enabled state of the external add-on library manager. - * - * @param enabled the enabled state. - * @throws IOException if an error occurs. - */ - public void setEnabled(boolean enabled) throws IOException { - try { - lock.lock(); - if (this.enabled != enabled) { - this.enabled = enabled; - if (enabled) { - startWatching(); - } else { - stopWatching(); - } - } - } finally { - lock.unlock(); - } - } - - /** - * Gets the path to the external add-on libraries. - * - * @return the path to the external add-on libraries. - */ - public Path getExternalLibraryPath() { - try { - lock.lock(); - return externalLibraryPath; - } finally { - lock.unlock(); - } - } - - /** - * Sets the path to the external add-on libraries. - * - * @param path the path to the external add-on libraries. - * @throws IOException if an error occurs. - */ - public void setExternalLibraryPath(Path path) throws IOException { - if (path != null && path.equals(externalLibraryPath)) { - return; - } - try { - lock.lock(); - externalLibraryPath = path; - stopWatching(); - if (path != null && enabled) { - startWatching(); - } - } finally { - lock.unlock(); - } - } - - /** Stops watching the external add-on library directory. */ - private void stopWatching() throws IOException { - try { - lock.lock(); - if (directoryWatcher != null) { - directoryWatcher.close(); - directoryWatcher = null; - } - } finally { - lock.unlock(); - } - } - - /** - * Starts watching the external add-on library directory. - * - * @throws IOException if an error occurs. - */ - private void startWatching() throws IOException { - try { - lock.lock(); - refreshAll(); - if (enabled && externalLibraryPath != null && Files.exists(externalLibraryPath)) { - if (directoryWatcher != null) { - directoryWatcher.watchAsync(); - } else { - directoryWatcher = createDirectoryWatcher(); - directoryWatcher.watchAsync(); - } - } - } finally { - lock.unlock(); - } - } - - /** - * Refreshes all the external add-on libraries. - * - * @throws IOException if an error occurs. - */ - private void refreshAll() throws IOException { - if (!initialised || !enabled || externalLibraryPath == null) { - return; - } - File[] directories = externalLibraryPath.toFile().listFiles(File::isDirectory); - if (directories != null) { - for (File directory : directories) { - try { - registerExternalAddOnLibrary(directory.toPath()); - } catch (IOException e) { - MapTool.showError(I18N.getText("library.dialog.read.failed", directory)); - } - } - } - var eventBus = new MapToolEventBus().getMainEventBus(); - eventBus.post(new ExternalAddonsUpdateEvent()); - } - - /** - * Makes the add-on library with the specified namespace available to MapTool. - * - * @param namespace the namespace of the add-on library to make available. - */ - public void importLibrary(String namespace) throws IOException { - if (enabled) { - var libInfo = namespaceInfoMap.get(namespace.toLowerCase()); - if (libInfo != null) { - var lib = new AddOnLibraryImporter().importFromDirectory(libInfo.backingDirectory()); - addOnLibraryManager.registerLibrary(lib); - } - } - } - - /** - * Checks to see if the specified path is an ignored sub-path when watching the external add-on - * - * @param path the path to check. - * @return {@code true} if the path is an ignored sub-path. - */ - private boolean isIgnoredSubPath(Path path) { - if (path.startsWith(".git")) { - return true; - } - - if (path.getFileName().toString().toLowerCase().endsWith(".mtlib")) { - return true; - } - - return false; - } - - /** - * Stops the add-on library with the specified namespace from being available to MapTool. - * - * @return the namespace of the add-on library to stop being available. - * @throws IOException if an error occurs. - */ - private DirectoryWatcher createDirectoryWatcher() throws IOException { - return DirectoryWatcher.builder() - .path(externalLibraryPath) - .listener( - event -> { - try { - int basePathNameCount = externalLibraryPath.getNameCount(); - var path = event.path(); - if (path.getNameCount() <= basePathNameCount) { - return; - } - if (!path.startsWith(externalLibraryPath)) { - return; - } - var subPath = externalLibraryPath.relativize(path); - if (isIgnoredSubPath(subPath)) { - return; - } - path = externalLibraryPath.resolve(path.getName(basePathNameCount)); - switch (event.eventType()) { - case CREATE -> registerExternalAddOnLibrary(path); - case DELETE -> { - if (path.toFile().exists()) { - refreshExternalAddOnLibrary(path); - } else { - deregisterExternalAddOnLibrary(path); - } - } - case MODIFY -> refreshExternalAddOnLibrary(path); - } - } catch (IOException e) { - MapTool.showError(I18N.getText("library.dialog.read.failed", event.path())); - } - }) - .build(); - } -} diff --git a/src/main/java/net/rptools/maptool/model/library/addon/ExternalLibraryInfo.java b/src/main/java/net/rptools/maptool/model/library/addon/ExternalLibraryInfo.java deleted file mode 100644 index eed3446292..0000000000 --- a/src/main/java/net/rptools/maptool/model/library/addon/ExternalLibraryInfo.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.model.library.addon; - -import java.nio.file.Path; -import net.rptools.maptool.model.library.LibraryInfo; - -/** - * Represents the information about external add-on library. - * - * @param namespace The namespace of the add-on. - * @param libraryInfo The library info of the add-on. - * @param updatedOnDisk Whether the add-on has been updated on disk. - * @param isInstalled Whether the add-on is installed. - * @param backingDirectory The backing directory of the add-on. - * @param subDirectoryName The subdirectory name of the add-on in the add-on development dir. - */ -public record ExternalLibraryInfo( - String namespace, - LibraryInfo libraryInfo, - boolean updatedOnDisk, - boolean isInstalled, - Path backingDirectory, - String subDirectoryName) {} diff --git a/src/main/java/net/rptools/maptool/util/FileUtil.java b/src/main/java/net/rptools/maptool/util/FileUtil.java index a08240193b..577db94ab4 100644 --- a/src/main/java/net/rptools/maptool/util/FileUtil.java +++ b/src/main/java/net/rptools/maptool/util/FileUtil.java @@ -21,11 +21,6 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; import net.rptools.maptool.client.AppUtil; /** @@ -203,38 +198,4 @@ public static File cleanFileName(String path, String fileName, String extension) public static File cleanFileName(String fileName, String extension) { return cleanFileName(null, fileName, extension); } - - /** - * Uses {@link Files#list(Path)} to recursively list all paths from a directory. The result - * includes directories. - * - * @param dir The directory to list recursively. - * @return A stream with all files. - * @throws IOException if an I/O error occurs while opening the directory, or any of its recursive - * children. - */ - public static Stream listRecursively(Path dir) throws IOException { - try (var pathStream = Files.list(dir)) { - var list = pathStream.collect(Collectors.toSet()); - try { - return Stream.concat( - list.stream(), - list.stream() - .flatMap( - p -> { - if (Files.isDirectory(p)) - try { - return listRecursively(p).map(p::resolve); - } catch (IOException e) { - throw new RuntimeException(e); - } - return null; - }) - .filter(Objects::nonNull)); - } catch (RuntimeException e) { - // This is to bypass an uncaught exception in the above lambda that calls in place. - throw (IOException) e.getCause(); - } - } - } } diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 09fb8740fb..92cf3b9bab 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2931,8 +2931,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can''t be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2974,7 +2972,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2985,41 +2982,8 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing \ purposes only.
Within your add-on use
lib://net.rptools.maptool/css/mt-stat-sheet.css \ or
lib://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = ### Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can''t convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_cs.properties b/src/main/resources/net/rptools/maptool/language/i18n_cs.properties index 351b4b7352..649beb40ca 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_cs.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_cs.properties @@ -2735,8 +2735,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_da.properties b/src/main/resources/net/rptools/maptool/language/i18n_da.properties index 044d1e358d..ae876d3184 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_da.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_da.properties @@ -2735,8 +2735,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Kan ikke konvertere {0} til {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_en.properties b/src/main/resources/net/rptools/maptool/language/i18n_en.properties index 99b6fbdfba..00142e12aa 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_en.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_en.properties @@ -2738,8 +2738,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties b/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties index 5a8aad2044..3c51b44586 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties @@ -2786,8 +2786,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties b/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties index 09d93d7d9f..ccfdf50ff3 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties @@ -1198,7 +1198,7 @@ action.error.noMapBoard = There is no map image. Use "Edit # hard-coded in the application, but that's OSX for you. ;-) action.exit = E&xit action.exit.description = Exit out of MapTool. -action.exportCampaignAs = +action.exportCampaignAs = action.exportCampaignAs.description = Export current campaign to a version compatible with older MapTool releases. action.exportScreenShot = Screenshot action.exportScreenShot.title = Export Screenshot @@ -2786,8 +2786,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_es.properties b/src/main/resources/net/rptools/maptool/language/i18n_es.properties index 14755c19bf..73978e60ee 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_es.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_es.properties @@ -2735,8 +2735,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_fr.properties b/src/main/resources/net/rptools/maptool/language/i18n_fr.properties index 3365b032b7..4c3f041e2b 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_fr.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_fr.properties @@ -2735,8 +2735,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_it.properties b/src/main/resources/net/rptools/maptool/language/i18n_it.properties index a3e536cab5..4414534677 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_it.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_it.properties @@ -2735,8 +2735,8 @@ library.dialog.table.removeData = Rimuovi Libreria e Dati library.dialog.delete.confirm = Sei sicuro di voler rimuovere la libreria aggiuntiva {0}? library.dialog.deleteData.confirm = Sei sicuro di voler rimuovere la libreria aggiuntiva {0} e tutti i suoi dati? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Impossibile convertire {0} in {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_nl.properties b/src/main/resources/net/rptools/maptool/language/i18n_nl.properties index 538c037b33..887c95b3f3 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_nl.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_nl.properties @@ -2735,8 +2735,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaaord +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_pl.properties b/src/main/resources/net/rptools/maptool/language/i18n_pl.properties index bfac031e76..3526e92446 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_pl.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_pl.properties @@ -2735,8 +2735,8 @@ library.dialog.table.removeData = Usuń bibliotekę i dane library.dialog.delete.confirm = Czy na pewno chcesz usunąć bibliotekę dodatków {0}? library.dialog.deleteData.confirm = Czy na pewno chcesz usunąć bibliotekę dodatków {0} i wszystkie jej dane? library.dialog.copy.title = Skopiowany CSS służy wyłącznie do testowania.
W ramach dodatku użyj
lib\://net.rptools.maptool/css/mt-stat-sheet.css lub
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Skopiuj motyw CSS do clipboard -library.dialog.copyMTStatSheetTheme = Skopiuj motyw arkusza statystyk do clipboard +library.dialog.copyMTThemeCSS = Skopiuj motyw CSS do clipbaord +library.dialog.copyMTStatSheetTheme = Skopiuj motyw arkusza statystyk do clipbaord # Game Data data.error.cantConvertTo = Nie można przekonwertować {0} na {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_pt.properties b/src/main/resources/net/rptools/maptool/language/i18n_pt.properties index b444562a07..fd4b60ce42 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_pt.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_pt.properties @@ -2735,8 +2735,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_ru.properties b/src/main/resources/net/rptools/maptool/language/i18n_ru.properties index 4dc779d484..55a4c68fdc 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_ru.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_ru.properties @@ -2735,8 +2735,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_si.properties b/src/main/resources/net/rptools/maptool/language/i18n_si.properties index 28e8c3afe4..756327bba4 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_si.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_si.properties @@ -2738,8 +2738,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_sv.properties b/src/main/resources/net/rptools/maptool/language/i18n_sv.properties index 2f6fdfc08c..cfb14cf531 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_sv.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_sv.properties @@ -2735,8 +2735,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_uk.properties b/src/main/resources/net/rptools/maptool/language/i18n_uk.properties index f22daa4c87..9ce4d742f2 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_uk.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_uk.properties @@ -2735,8 +2735,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. diff --git a/src/main/resources/net/rptools/maptool/language/i18n_zh.properties b/src/main/resources/net/rptools/maptool/language/i18n_zh.properties index 475012c672..7ad427eff6 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_zh.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_zh.properties @@ -2735,8 +2735,8 @@ library.dialog.table.removeData = Remove Library and Data library.dialog.delete.confirm = Are you sure you want to remove add-on library {0}? library.dialog.deleteData.confirm = Are you sure you want to remove add-on library {0} and all its data? library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From eb38384ef5abb8c330bbecb58abc2d05af4e79e2 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:28 +0800 Subject: [PATCH 108/146] New translations i18n.properties (Danish) --- .../maptool/language/i18n_da_DK.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_da_DK.properties b/src/main/resources/net/rptools/maptool/language/i18n_da_DK.properties index 1e95da2ea1..b1020b9347 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_da_DK.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_da_DK.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Importer udvidelsesbibliotek. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Beskrivelse library.dialog.addon.readMeFile = Vis "Læs mig" fil library.dialog.addon.authors = Forfattere library.dialog.addon.license = Licens -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = Vis licensfil library.dialog.addon.giturl = Git URL library.dialog.addon.website = Hjemmeside @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Kan ikke konvertere {0} til {1}. From 9fdf631ee6f7493af5716d1c39a87922cb5dd903 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:30 +0800 Subject: [PATCH 109/146] New translations i18n.properties (French) --- .../maptool/language/i18n_fr_FR.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_fr_FR.properties b/src/main/resources/net/rptools/maptool/language/i18n_fr_FR.properties index 791a2d53f1..ae40c17d9c 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_fr_FR.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_fr_FR.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 65a49f450cfa7e41478959e22a8252bfa3714575 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:32 +0800 Subject: [PATCH 110/146] New translations i18n.properties (Spanish) --- .../maptool/language/i18n_es_ES.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_es_ES.properties b/src/main/resources/net/rptools/maptool/language/i18n_es_ES.properties index 241fe34781..97a5b96d3e 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_es_ES.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_es_ES.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 1f17d50cb57b4868356dddf8dad35e60b1892ec3 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:33 +0800 Subject: [PATCH 111/146] New translations i18n.properties (Czech) --- .../maptool/language/i18n_cs_CZ.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_cs_CZ.properties b/src/main/resources/net/rptools/maptool/language/i18n_cs_CZ.properties index c80b2819ef..232bc79477 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_cs_CZ.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_cs_CZ.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From bf49281d1426315568154dbb3be303db27bfa265 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:35 +0800 Subject: [PATCH 112/146] New translations i18n.properties (German) --- .../maptool/language/i18n_de_DE.properties | 40 +------------------ 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties b/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties index fb1032259b..8f8a45c117 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Ungültige Bibliotheksdefinition. library.error.emptyName = Der Name einer Zusatzbibliothek darf nicht leer sein. library.error.emptyVersion = Die Version der Zusatzbibliothek {0} darf nicht leer sein. library.dialog.import.title = Importiere Zusatzbibliothek. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error beim Drop-In-Bibliotheksimport. library.import.error = Fehler beim Drop-In-Bibliotheksimport von {0}. library.import.error.notGM = Nur SL kann eine Drop-in-Bibliothek importieren. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Beschreibung library.dialog.addon.readMeFile = Read-Me-Datei anzeigen library.dialog.addon.authors = Autoren library.dialog.addon.license = Lizenz -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = Lizenzdatei anzeigen library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2791,41 +2788,8 @@ library.dialog.table.removeData = Bibliothek und Daten entfernen library.dialog.delete.confirm = Soll die Add-on-Bibliothek {0} wirklich entfernt werden? library.dialog.deleteData.confirm = Soll die Add-on-Bibliothek {0} mit allen ihren Daten wirklich entfernt werden? library.dialog.copy.title = Das kopierte CSS dient nur Testzwecken.
Im Add-on benutze
lib\://net.rptools.maptool/css/mt-stat-sheet.css oder
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = CSS-Theme ins Clipboard kopieren -library.dialog.copyMTStatSheetTheme = Stat Sheet-Theme ins Clipboard kopieren -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Kann {0} nicht in {1} konvertieren. From b885ac0c6f5b7f06300aeac182da90487e28fd3f Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:36 +0800 Subject: [PATCH 113/146] New translations i18n.properties (Italian) --- .../maptool/language/i18n_it_IT.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_it_IT.properties b/src/main/resources/net/rptools/maptool/language/i18n_it_IT.properties index 9361c9cdb5..c6eba846f2 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_it_IT.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_it_IT.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Definizione libreria non valida. library.error.emptyName = Il nome non può essere vuoto per la libreria aggiuntiva. library.error.emptyVersion = La versione non può essere vuota per la libreria aggiuntiva {0}. library.dialog.import.title = Importa Libreria Aggiuntiva. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = Errore di IO durante l'importazione della libreria Drop In. library.import.error = Errore nell'importazione della Libreria Drop In {0}. library.import.error.notGM = Solo il GM può importare librerie Drop In. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Descrizione library.dialog.addon.readMeFile = Leggi file Read Me library.dialog.addon.authors = Autori library.dialog.addon.license = Licenza -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = Visualizza File Licenza library.dialog.addon.giturl = Git URL library.dialog.addon.website = Sito @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Sei sicuro di voler rimuovere la libreria ag library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Impossibile convertire {0} in {1}. From f7963aa3701992580051cbcbb410bea3bb7b8c25 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:38 +0800 Subject: [PATCH 114/146] New translations i18n.properties (Japanese) --- .../maptool/language/i18n_ja_JP.properties | 40 +------------------ 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties b/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties index 91f8cbe3d7..6f4ca28a10 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_ja_JP.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = ライブラリー定義が無効です。 library.error.emptyName = 拡張ライブラリーの名称は空白にできません。 library.error.emptyVersion = 拡張ライブラリー {0} のバージョンは空白にできません。 library.dialog.import.title = 拡張ライブラリーを取り込む。 -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = ドロップされたライブラリーの取り込み時に入出力エラーが発生しました。 library.import.error = ドロップされたライブラリー {0} の取り込み時に力エラーが発生しました。 library.import.error.notGM = ライブラリーをドロップして取込めるのはGMのみです。 @@ -2782,7 +2780,6 @@ library.dialog.addon.description = 説明 library.dialog.addon.readMeFile = 『はじめにお読みください』を表示 library.dialog.addon.authors = 作者 library.dialog.addon.license = 許諾 -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = 許諾ファイルを表示 library.dialog.addon.giturl = Git URL library.dialog.addon.website = ウェブサイト @@ -2791,41 +2788,8 @@ library.dialog.table.removeData = ライブラリーとデータを削除 library.dialog.delete.confirm = 拡張ライブラリー『{0}』を削除してもよろしいでしょうか? library.dialog.deleteData.confirm = 拡張ライブラリー『{0}』と、全ての付随データを削除してもよろしいでしょうか? library.dialog.copy.title = コピーされたCSSはテストで使用する事を目的としています。
拡張ライブラリーでの使用
lib\://net.rptools.maptool/css/mt-stat-sheet.css または
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = CSSテーマをクリップボードにコピーする -library.dialog.copyMTStatSheetTheme = データシートのテーマをクリップボードにコピーする -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = {0} を {1} に変換する事ができません。 From 96625eeaeedf794c662c0a6071848da81be2332b Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:39 +0800 Subject: [PATCH 115/146] New translations i18n.properties (Dutch) --- .../maptool/language/i18n_nl_NL.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_nl_NL.properties b/src/main/resources/net/rptools/maptool/language/i18n_nl_NL.properties index 47867eee19..98888664a5 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_nl_NL.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_nl_NL.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 58e16fa22d09fd4ac614dc9c8718322b4537a00f Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:41 +0800 Subject: [PATCH 116/146] New translations i18n.properties (Polish) --- .../maptool/language/i18n_pl_PL.properties | 40 +------------------ 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_pl_PL.properties b/src/main/resources/net/rptools/maptool/language/i18n_pl_PL.properties index d889da57cd..ffa1aaa8f8 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_pl_PL.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_pl_PL.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Nieprawidłowa definicja biblioteki. library.error.emptyName = Nazwa nie może być pusta dla biblioteki dodatków. library.error.emptyVersion = Wersja nie może być pusta dla biblioteki dodatków {0}. library.dialog.import.title = Importuj bibliotekę dodatków. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = Błąd IO importu biblioteki Drop In. library.import.error = Błąd importu biblioteki Drop In {0}. library.import.error.notGM = Tylko GM może importować Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Opis library.dialog.addon.readMeFile = Zobacz plik ReadMe library.dialog.addon.authors = Autorzy library.dialog.addon.license = Licencja -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = Pokaż licencję library.dialog.addon.giturl = Git URL library.dialog.addon.website = Strona WWW @@ -2791,41 +2788,8 @@ library.dialog.table.removeData = Usuń bibliotekę i dane library.dialog.delete.confirm = Czy na pewno chcesz usunąć bibliotekę dodatków {0}? library.dialog.deleteData.confirm = Czy na pewno chcesz usunąć bibliotekę dodatków {0} i wszystkie jej dane? library.dialog.copy.title = Skopiowany CSS służy wyłącznie do testowania.
W ramach dodatku użyj
lib\://net.rptools.maptool/css/mt-stat-sheet.css lub
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Skopiuj motyw CSS do clipbaord -library.dialog.copyMTStatSheetTheme = Skopiuj motyw arkusza statystyk do clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Nie można przekonwertować {0} na {1}. From 0212d959f3bb501b3efd6ba07ac73670f71eb432 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:42 +0800 Subject: [PATCH 117/146] New translations i18n.properties (Russian) --- .../maptool/language/i18n_ru_RU.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_ru_RU.properties b/src/main/resources/net/rptools/maptool/language/i18n_ru_RU.properties index 1f0de31cbc..b8bb085490 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_ru_RU.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_ru_RU.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 32a226fd765866de6cb83c386e54d6babd70c1fb Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:44 +0800 Subject: [PATCH 118/146] New translations i18n.properties (Swedish) --- .../maptool/language/i18n_sv_SE.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_sv_SE.properties b/src/main/resources/net/rptools/maptool/language/i18n_sv_SE.properties index 48575923db..d6296d00da 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_sv_SE.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_sv_SE.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From af29823e4f6730037afb8c2dbd42d8db4e6cde31 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:45 +0800 Subject: [PATCH 119/146] New translations i18n.properties (Ukrainian) --- .../maptool/language/i18n_uk_UA.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_uk_UA.properties b/src/main/resources/net/rptools/maptool/language/i18n_uk_UA.properties index cca9eea402..184d06acfc 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_uk_UA.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_uk_UA.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 3ccd2f24cbf66139ea9de378535146e09f66a5d7 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:47 +0800 Subject: [PATCH 120/146] New translations i18n.properties (Chinese Simplified) --- .../maptool/language/i18n_zh_CN.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_zh_CN.properties b/src/main/resources/net/rptools/maptool/language/i18n_zh_CN.properties index 09feca624f..5a60f28051 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_zh_CN.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_zh_CN.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = 无效的库定义。 library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = 导入附加组件库。 -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From e02240f90ccef2e12bd599753cbf0e0db81839f2 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:48 +0800 Subject: [PATCH 121/146] New translations i18n.properties (Portuguese, Brazilian) --- .../maptool/language/i18n_pt_BR.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_pt_BR.properties b/src/main/resources/net/rptools/maptool/language/i18n_pt_BR.properties index 7bffb6fb5d..e51cbb2f56 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_pt_BR.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_pt_BR.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Autores library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 9024fb84184c3119ca95ebfd1e9ef4518f2c4baf Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:50 +0800 Subject: [PATCH 122/146] New translations i18n.properties (English, Australia) --- .../maptool/language/i18n_en_AU.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties b/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties index 59505c1c97..a029ed192e 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_en_AU.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From 3ef945bafe3d5da68aea1d5f1416a0f53ca4701e Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:52 +0800 Subject: [PATCH 123/146] New translations i18n.properties (English, United Kingdom) --- .../maptool/language/i18n_en_GB.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties b/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties index 06b2802bda..58978b32ab 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_en_GB.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From d96ecdc330160d8db33d8d9fe14a084dd9999bb4 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:27:53 +0800 Subject: [PATCH 124/146] New translations i18n.properties (Sinhala) --- .../maptool/language/i18n_si_LK.properties | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_si_LK.properties b/src/main/resources/net/rptools/maptool/language/i18n_si_LK.properties index 6265fd77c3..321e956cd3 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_si_LK.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_si_LK.properties @@ -2742,8 +2742,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can't be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2782,7 +2780,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2793,39 +2790,6 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing purposes only.
Within your add-on use
lib\://net.rptools.maptool/css/mt-stat-sheet.css or
lib\://net.rptools.maptool/css/mt-theme.css library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = \#\#\# Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content # Game Data data.error.cantConvertTo = Can't convert {0} to {1}. From d5634193a7fa45c12af6659ffc8f4162df2467ce Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Fri, 18 Oct 2024 00:53:48 +0800 Subject: [PATCH 125/146] Update source file i18n.properties --- .../rptools/maptool/language/i18n.properties | 40 +------------------ 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 09fb8740fb..92cf3b9bab 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2931,8 +2931,6 @@ library.error.invalidDefinition = Invalid library definition. library.error.emptyName = Name can not be empty for add-on library. library.error.emptyVersion = Version can''t be empty for add-on library {0}. library.dialog.import.title = Import Add-On Library. -library.dialog.reimport = Reimport -library.dialog.import = Import library.import.ioError = IO Error importing Drop In Library. library.import.error = Error importing Drop In Library {0}. library.import.error.notGM = Only GM can import a Drop in Library. @@ -2974,7 +2972,6 @@ library.dialog.addon.description = Description library.dialog.addon.readMeFile = View Read Me File library.dialog.addon.authors = Authors library.dialog.addon.license = License -library.dialog.addon.readme = Read Me library.dialog.addon.licenseFile = View License File library.dialog.addon.giturl = Git URL library.dialog.addon.website = Website @@ -2985,41 +2982,8 @@ library.dialog.deleteData.confirm = Are you sure you want to remove add-on libra library.dialog.copy.title = The copied CSS is for testing \ purposes only.
Within your add-on use
lib://net.rptools.maptool/css/mt-stat-sheet.css \ or
lib://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipboard -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipboard -library.dialog.externalAddon = Enable external add-on libraries (development purposes only) -library.dialog.addon.subdir = Sub Directory -library.dialog.addon.imported = Imported -library.dialog.addon.updated = Updated -library.dialog.addon.refresh = Refresh -library.dialog.addon.createNew = Create New Add-On -library.dialog.addon.createMTLib = Create .mtlib file -library.dialog.directory.label = Add On Directory -library.dialog.parentdir.label = Parent Directory -library.dialog.read.failed = Error trying to read details of add-on in {0}, check library.json file format -library.dialog.import.failed = Error trying to read details of add-on in {0}, check library.json file format and library contents. -library.dialog.addon.createExample=Create Example Files -library.dialog.addon.createEvents=Events -library.dialog.addons.authorsComma=Authors (comma seperated) -library.dialog.addon.createSlash=Slash Commands -library.dialog.addon.createMTSProps=Macro Script Properties -library.dialog.addon.udf=User Defined Functions -library.dialog.addon.create.readeMe = ### Read Me\nPlace your Read Me information here, this file supports markup. -library.dialog.addon.create.licenseText = Place your license details in this file. -library.dialog.addon.create.shortDesc = Add-on Short Description -library.dialog.addon.create.longDesc = Add-on Long Description -library.dialog.addon.create.noSuchDir = Parent directory {0} for add-on does not exist. -library.dialog.addon.create.dirExists = Directory {0} for add-on already exists, chose a different directory. -library.dialog.create.parentDir.title = Choose Parent Directory for the Add-On -library.dialog.failedToCreateDir = Failed to create directory {0} for add-on. -library.dialog.failedToCreateFile = Failed to create file {0} for add-on. -library.dialog.create.exampleSlashCmdDesc = Example Slash Command -library.dialog.create.autoExecDesc = Auto Exec in macro link example -library.dialog.create.noAutoExecDesc = Non Auto Exec in macro link example -library.dialog.error.displayingAddons = Error displaying add-on libraries -library.dialog.addon.fileFilter = Add-On Libraries (*.mtlib) -library.dialog.addon.errorCreatingMTLib = Error Creating .mtlib file -library.dialog.addon.create.mtsComment = Replace this file ({0}) with your content +library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord +library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord # Game Data data.error.cantConvertTo = Can''t convert {0} to {1}. From 32dcaa505b5b6a2d7660ca30ab93fec5b8c1d835 Mon Sep 17 00:00:00 2001 From: Reverend <45483160+bubblobill@users.noreply.github.com> Date: Sun, 20 Oct 2024 04:28:53 +0800 Subject: [PATCH 126/146] New translations i18n.properties (German) --- .../net/rptools/maptool/language/i18n_de_DE.properties | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties b/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties index 8f8a45c117..e95a48a6b4 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n_de_DE.properties @@ -1877,8 +1877,8 @@ macro.function.general.unknownProperty = Fehler beim Ausführen von macro.function.general.unknownTokenOnMap = Fehler beim Ausführen von "{0}"\: der Spielmarkenname oder die Id "{1}" ist auf der Karte "{2}" unbekannt. macro.function.general.wrongNumParam = Funktion "{0}" benötigt exakt {1} Parameter; {2} wurden angegeben. macro.function.general.listCannotBeEmpty = {0}\: String-Liste bei Argument {1} darf nicht leer sein -macro.function.general.wrongParamType = A parameter is the wrong type. -macro.function.general.paramCannotBeEmpty = A parameter is empty. +macro.function.general.wrongParamType = Ein Parameter ist vom falschen Typ. +macro.function.general.paramCannotBeEmpty = Ein Parameter ist leer. # Token Distance functions # I.e. ONE_TWO_ONE or ONE_ONE_ONE macro.function.getDistance.invalidMetric = Ungültiger Metriktyp "{0}". @@ -2267,7 +2267,7 @@ msg.info.stopWebEndWebPoint = Stoppe Webserver-Endpoint. # code adds that, if appropriate, based on the situation. msg.info.versionFile = KANN DIE VERSIONSDATEI NICHT FINDEN msg.info.assetsDisabled = Der SL hat das Einfügen von Spieler-Assets abgeschaltet. -msg.info.server.upnp.discovering = Discovering UPnP gateways... +msg.info.server.upnp.discovering = Suche UPnP-Gateways... msg.title.exportMacro = Exportiere Makro @@ -2788,8 +2788,8 @@ library.dialog.table.removeData = Bibliothek und Daten entfernen library.dialog.delete.confirm = Soll die Add-on-Bibliothek {0} wirklich entfernt werden? library.dialog.deleteData.confirm = Soll die Add-on-Bibliothek {0} mit allen ihren Daten wirklich entfernt werden? library.dialog.copy.title = Das kopierte CSS dient nur Testzwecken.
Im Add-on benutze
lib\://net.rptools.maptool/css/mt-stat-sheet.css oder
lib\://net.rptools.maptool/css/mt-theme.css -library.dialog.copyMTThemeCSS = Copy CSS Theme to clipbaord -library.dialog.copyMTStatSheetTheme = Copy Stat Sheet Theme to clipbaord +library.dialog.copyMTThemeCSS = CSS-Theme ins Clipboard kopieren +library.dialog.copyMTStatSheetTheme = Stat-Sheet-Theme ins Clipboard kopieren # Game Data data.error.cantConvertTo = Kann {0} nicht in {1} konvertieren. From 61307e07f0757ae93d9d3e5a6267a952003b3310 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 22 Oct 2024 21:42:46 +0100 Subject: [PATCH 127/146] Add deflate as a supported REST response encoding Some HTTP servers don't parse Accept-Encoding headers as they should and just assume that deflate is commonly available. okhttp3 has built-in support for transparently adding gzip as an accepted encoding, and decoding the response body, which is how gzip previously Just Worked. This built-in encoding handler is conditional on the user not passing in any Accept-Encoding headers, which is assumed to indicate that the caller intends to somehow handle the encoded response body themself. We can implement our own transparent decoding support using the same mechanism that okhttp3 does internally and add an Interceptor callback that adds headers before the request and returns a new response with the encoding headers removed and the body wrapped in decoding readers. This is strictly more correct than the okhttp3 built-in decoder because Content-Encoding can be a sequence of encodings applied in order that have to be decoded in reverse order. --- .../client/functions/RESTfulFunctions.java | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java b/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java index 36ba87b3b1..10973833cf 100644 --- a/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java @@ -21,9 +21,11 @@ import java.math.BigDecimal; import java.net.URI; import java.net.URISyntaxException; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.zip.Inflater; import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.language.I18N; @@ -33,11 +35,17 @@ import net.rptools.parser.VariableResolver; import net.rptools.parser.function.AbstractFunction; import okhttp3.Headers; +import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.BufferedSource; +import okio.GzipSource; +import okio.InflaterSource; +import okio.Okio; /** * RESTful based functions REST.get, REST.post, REST.put, REST.patch, REST.delete @@ -67,7 +75,7 @@ public static RESTfulFunctions getInstance() { return instance; } - private final OkHttpClient client = new OkHttpClient(); + private final OkHttpClient client = buildClient(); private final Gson gson = new Gson(); @Override @@ -236,6 +244,72 @@ private Object executeClientCall(String functionName, Request request, boolean f } } + private OkHttpClient buildClient() { + return new OkHttpClient.Builder() + .addInterceptor( + (Interceptor.Chain chain) -> { + var oldRequest = chain.request(); + + // If the macro has passed its own Accept-Encoding + // it's indicating it expects to somehow handle it itself. + if (oldRequest.header("Accept-Encoding") != null) { + return chain.proceed(oldRequest); + } + + // Augment request saying we accept multiple content encodings + var newHeaders = + oldRequest + .headers() + .newBuilder() + .add("Accept-Encoding", "deflate") + .add("Accept-Encoding", "gzip") + .build(); + + var newRequest = oldRequest.newBuilder().headers(newHeaders).build(); + + var oldResponse = chain.proceed(newRequest); + + // Replace the response's request with the original one + var responseBuilder = oldResponse.newBuilder().request(oldRequest); + + // We might not have a body to decompress + var body = oldResponse.body(); + if (body != null) { + BufferedSource source = body.source(); + // The body may have been wrapped in an arbitrary encoding sequence + // and the server returns them in the order it encoded them + // so we wrap them with decoders in reverse order. + var encodings = oldResponse.headers().values("Content-Encoding"); + Collections.reverse(encodings); + for (var encoding : encodings) { + if ("deflate".equalsIgnoreCase(encoding)) { + var inflater = new Inflater(true); + source = Okio.buffer(new InflaterSource(source, inflater)); + } else if ("gzip".equalsIgnoreCase(encoding)) { + source = Okio.buffer(new GzipSource(source)); + } + } + + // Strip encoding and length headers as we've already handled them + var strippedHeaders = + oldResponse + .headers() + .newBuilder() + .removeAll("Content-Encoding") + .removeAll("Content-Length") + .build(); + responseBuilder.headers(strippedHeaders); + var contentType = MediaType.parse(oldResponse.header("Content-Type")); + // Construct a new body with an inferred Content-Length + var newBody = ResponseBody.create(contentType, -1L, source); + responseBuilder.body(newBody); + } + + return responseBuilder.build(); + }) + .build(); + } + private Headers buildHeaders(Map> headerMap) { Headers.Builder headerBuilder = new Headers.Builder(); From 71b92b792ec0da29ba9a33ad9d25bcb38283cf48 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Wed, 23 Oct 2024 17:11:04 -0700 Subject: [PATCH 128/146] Perform zone topology edits through server commands The `ServerCommand.addTopology()` and `removeTopology()` have been merged into a single `updateTopology()` that handles both drawing and erasing. It also applies the changes to the local client so caller's don't have to remember to do that. All callers were already well-behaved here, but this change just cleans them all up and avoids the repetition conditions. --- .../maptool/client/ClientMessageHandler.java | 30 ++++----------- .../client/ServerCommandClientImpl.java | 23 +++++------- .../client/functions/Topology_Functions.java | 9 +++-- .../tool/drawing/CrossTopologyTool.java | 13 +------ .../tool/drawing/DiamondTopologyTool.java | 14 +------ .../drawing/HollowDiamondTopologyTool.java | 14 +------ .../tool/drawing/HollowOvalTopologyTool.java | 13 +------ .../drawing/HollowRectangleTopologyTool.java | 13 +------ .../client/tool/drawing/OvalTopologyTool.java | 12 +----- .../tool/drawing/PolygonTopologyTool.java | 10 +---- .../tool/drawing/RectangleTopologyTool.java | 14 +------ .../ui/drawpanel/DrawPanelPopupMenu.java | 13 ++----- .../ui/token/dialog/edit/EditTokenDialog.java | 19 ++++++---- .../maptool/client/ui/zone/vbl/TokenVBL.java | 23 ------------ .../utilities/DungeonDraftImporter.java | 12 +++--- .../java/net/rptools/maptool/model/Zone.java | 37 +++---------------- .../rptools/maptool/server/ServerCommand.java | 15 ++------ .../maptool/server/ServerMessageHandler.java | 30 ++++----------- src/main/proto/message.proto | 3 +- src/main/proto/message_types.proto | 11 ++---- 20 files changed, 79 insertions(+), 249 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ClientMessageHandler.java b/src/main/java/net/rptools/maptool/client/ClientMessageHandler.java index 34d8031a18..29a1eab816 100644 --- a/src/main/java/net/rptools/maptool/client/ClientMessageHandler.java +++ b/src/main/java/net/rptools/maptool/client/ClientMessageHandler.java @@ -101,7 +101,7 @@ public void handleMessage(String id, byte[] message) { log.debug("{} got: {}", id, msgType); switch (msgType) { - case ADD_TOPOLOGY_MSG -> handle(msg.getAddTopologyMsg()); + case UPDATE_TOPOLOGY_MSG -> handle(msg.getUpdateTopologyMsg()); case BOOT_PLAYER_MSG -> handle(msg.getBootPlayerMsg()); case CHANGE_ZONE_DISPLAY_NAME_MSG -> handle(msg.getChangeZoneDisplayNameMsg()); case CLEAR_ALL_DRAWINGS_MSG -> handle(msg.getClearAllDrawingsMsg()); @@ -128,7 +128,6 @@ public void handleMessage(String id, byte[] message) { case REMOVE_LABEL_MSG -> handle(msg.getRemoveLabelMsg()); case REMOVE_TOKEN_MSG -> handle(msg.getRemoveTokenMsg()); case REMOVE_TOKENS_MSG -> handle(msg.getRemoveTokensMsg()); - case REMOVE_TOPOLOGY_MSG -> handle(msg.getRemoveTopologyMsg()); case REMOVE_ZONE_MSG -> handle(msg.getRemoveZoneMsg()); case RENAME_ZONE_MSG -> handle(msg.getRenameZoneMsg()); case RESTORE_ZONE_VIEW_MSG -> handle(msg.getRestoreZoneViewMsg()); @@ -701,20 +700,6 @@ private void handle(RemoveZoneMsg msg) { }); } - private void handle(RemoveTopologyMsg msg) { - EventQueue.invokeLater( - () -> { - var zoneGUID = GUID.valueOf(msg.getZoneGuid()); - var area = Mapper.map(msg.getArea()); - var topologyType = Zone.TopologyType.valueOf(msg.getType().name()); - - var zone = client.getCampaign().getZone(zoneGUID); - zone.removeTopology(area, topologyType); - - MapTool.getFrame().getZoneRenderer(zoneGUID).repaint(); - }); - } - private void handle(RemoveTokensMsg msg) { EventQueue.invokeLater( () -> { @@ -1018,17 +1003,16 @@ private void handle(ChangeZoneDisplayNameMsg changeZoneDisplayNameMsg) { }); } - private void handle(AddTopologyMsg addTopologyMsg) { + private void handle(UpdateTopologyMsg updateTopologyMsg) { EventQueue.invokeLater( () -> { - var zoneGUID = GUID.valueOf(addTopologyMsg.getZoneGuid()); - var area = Mapper.map(addTopologyMsg.getArea()); - var topologyType = Zone.TopologyType.valueOf(addTopologyMsg.getType().name()); + var zoneGUID = GUID.valueOf(updateTopologyMsg.getZoneGuid()); + var area = Mapper.map(updateTopologyMsg.getArea()); + var erase = updateTopologyMsg.getErase(); + var topologyType = Zone.TopologyType.valueOf(updateTopologyMsg.getType().name()); var zone = client.getCampaign().getZone(zoneGUID); - zone.addTopology(area, topologyType); - - MapTool.getFrame().getZoneRenderer(zoneGUID).repaint(); + zone.updateTopology(area, erase, topologyType); }); } diff --git a/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java b/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java index e1dbb46f86..ec83158f01 100644 --- a/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java +++ b/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java @@ -392,23 +392,18 @@ public void toggleTokenMoveWaypoint(GUID zoneGUID, GUID tokenGUID, ZonePoint cp) makeServerCall(Message.newBuilder().setToggleTokenMoveWaypointMsg(msg).build()); } - public void addTopology(GUID zoneGUID, Area area, Zone.TopologyType topologyType) { - var msg = - AddTopologyMsg.newBuilder() - .setZoneGuid(zoneGUID.toString()) - .setType(TopologyTypeDto.valueOf(topologyType.name())) - .setArea(Mapper.map(area)); - - makeServerCall(Message.newBuilder().setAddTopologyMsg(msg).build()); - } - - public void removeTopology(GUID zoneGUID, Area area, Zone.TopologyType topologyType) { + @Override + public void updateTopology(Zone zone, Area area, boolean erase, Zone.TopologyType topologyType) { var msg = - RemoveTopologyMsg.newBuilder() - .setZoneGuid(zoneGUID.toString()) + UpdateTopologyMsg.newBuilder() + .setZoneGuid(zone.getId().toString()) .setArea(Mapper.map(area)) + .setErase(erase) .setType(TopologyTypeDto.valueOf(topologyType.name())); - makeServerCall(Message.newBuilder().setRemoveTopologyMsg(msg).build()); + + // Update locally as well. + zone.updateTopology(area, erase, topologyType); + makeServerCall(Message.newBuilder().setUpdateTopologyMsg(msg).build()); } public void exposePCArea(GUID zoneGUID) { diff --git a/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java b/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java index c25326b9a0..12b58b972f 100644 --- a/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java +++ b/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java @@ -290,7 +290,7 @@ private void childEvaluateDrawEraseTopology(String functionName, List pa default -> null; }; if (newArea != null) { - TokenVBL.renderTopology(renderer, newArea, erase, topologyType); + MapTool.serverCommand().updateTopology(renderer.getZone(), newArea, erase, topologyType); } } } @@ -595,8 +595,9 @@ private void childEvaluateTransferTopology( } if (topologyFromToken) { - TokenVBL.renderTopology( - renderer, token.getTransformedTopology(topologyType), false, topologyType); + MapTool.serverCommand() + .updateTopology( + renderer.getZone(), token.getTransformedTopology(topologyType), false, topologyType); if (delete) { token.setTopology(topologyType, null); } @@ -605,7 +606,7 @@ private void childEvaluateTransferTopology( token.setTopology( topologyType, TokenVBL.getMapTopology_transformed(renderer, token, topologyType)); if (delete) { - TokenVBL.renderTopology(renderer, topology, true, topologyType); + MapTool.serverCommand().updateTopology(renderer.getZone(), topology, true, topologyType); } } } diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/CrossTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/CrossTopologyTool.java index 226c75fe81..e7a6ac75a9 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/CrossTopologyTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/CrossTopologyTool.java @@ -91,18 +91,9 @@ public void mousePressed(MouseEvent e) { GraphicsUtil.createLine(1, new Point2D.Double(x1, y1), new Point2D.Double(x2, y2)); area.add( GraphicsUtil.createLine(1, new Point2D.Double(x1, y2), new Point2D.Double(x2, y1))); + MapTool.serverCommand() + .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - if (isEraser(e)) { - getZone().removeTopology(area); - MapTool.serverCommand() - .removeTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } else { - getZone().addTopology(area); - MapTool.serverCommand() - .addTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } - renderer.repaint(); - // TODO: send this to the server cross = null; } setIsEraser(isEraser(e)); diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTopologyTool.java index f8c77dc832..20a46db53b 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTopologyTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTopologyTool.java @@ -79,18 +79,8 @@ public void mousePressed(MouseEvent e) { return; } Area area = new ShapeDrawable(diamond, false).getArea(getZone()); - if (isEraser(e)) { - getZone().removeTopology(area); - MapTool.serverCommand() - .removeTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } else { - getZone().addTopology(area); - MapTool.serverCommand() - .addTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } - renderer.repaint(); - // TODO: send this to the server - + MapTool.serverCommand() + .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); diamond = null; } setIsEraser(isEraser(e)); diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowDiamondTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/HollowDiamondTopologyTool.java index 2b56df06c8..cdf7d9bc8e 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowDiamondTopologyTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/HollowDiamondTopologyTool.java @@ -78,18 +78,8 @@ public void mousePressed(MouseEvent e) { return; } Area area = new Area(diamond); - if (isEraser(e)) { - getZone().removeTopology(area); - MapTool.serverCommand() - .removeTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } else { - getZone().addTopology(area); - MapTool.serverCommand() - .addTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } - renderer.repaint(); - // TODO: send this to the server - + MapTool.serverCommand() + .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); diamond = null; } setIsEraser(isEraser(e)); diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowOvalTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/HollowOvalTopologyTool.java index 722e1cd1a3..93076a42bf 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowOvalTopologyTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/HollowOvalTopologyTool.java @@ -95,17 +95,8 @@ public void mousePressed(MouseEvent e) { area.subtract(innerArea); } - if (isEraser(e)) { - getZone().removeTopology(area); - MapTool.serverCommand() - .removeTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } else { - getZone().addTopology(area); - MapTool.serverCommand() - .addTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } - renderer.repaint(); - + MapTool.serverCommand() + .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); oval = null; } diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowRectangleTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/HollowRectangleTopologyTool.java index eff224ca08..4cdba8717e 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowRectangleTopologyTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/HollowRectangleTopologyTool.java @@ -89,17 +89,8 @@ public void mousePressed(MouseEvent e) { new Area(new java.awt.Rectangle(x1 + 1, y1 + 1, x2 - x1 - 2, y2 - y1 - 2)); area.subtract(innerArea); } - if (isEraser(e)) { - getZone().removeTopology(area); - MapTool.serverCommand() - .removeTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } else { - getZone().addTopology(area); - MapTool.serverCommand() - .addTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } - renderer.repaint(); - // TODO: send this to the server + MapTool.serverCommand() + .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); rectangle = null; } setIsEraser(isEraser(e)); diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTopologyTool.java index 32b994ac70..50293f702f 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTopologyTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTopologyTool.java @@ -83,16 +83,8 @@ public void mousePressed(MouseEvent e) { oval.getEndPoint().y, 10); - if (isEraser(e)) { - getZone().removeTopology(area); - MapTool.serverCommand() - .removeTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } else { - getZone().addTopology(area); - MapTool.serverCommand() - .addTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } - renderer.repaint(); + MapTool.serverCommand() + .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); oval = null; } setIsEraser(isEraser(e)); diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTopologyTool.java index 463d78e6c0..c4eb1e4f45 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTopologyTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTopologyTool.java @@ -97,14 +97,8 @@ protected void completeDrawable(Pen pen, Drawable drawable) { } else { area = new Area(((ShapeDrawable) drawable).getShape()); } - if (pen.isEraser()) { - getZone().removeTopology(area); - MapTool.serverCommand().removeTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } else { - getZone().addTopology(area); - MapTool.serverCommand().addTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } - renderer.repaint(); + MapTool.serverCommand() + .updateTopology(getZone(), area, pen.isEraser(), getZone().getTopologyTypes()); } @Override diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTopologyTool.java index 8bf5a95d39..cb9a045595 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTopologyTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTopologyTool.java @@ -80,18 +80,8 @@ public void mousePressed(MouseEvent e) { int y2 = Math.max(rectangle.getStartPoint().y, rectangle.getEndPoint().y); Area area = new Area(new java.awt.Rectangle(x1, y1, x2 - x1, y2 - y1)); - if (isEraser(e)) { - getZone().removeTopology(area); - MapTool.serverCommand() - .removeTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } else { - getZone().addTopology(area); - MapTool.serverCommand() - .addTopology(getZone().getId(), area, getZone().getTopologyTypes()); - } - renderer.repaint(); - // TODO: send this to the server - + MapTool.serverCommand() + .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); rectangle = null; } setIsEraser(isEraser(e)); diff --git a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java index 671e9a5e3a..292c8fc78b 100644 --- a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java +++ b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java @@ -584,16 +584,9 @@ private void VblTool(Drawable drawable, boolean pathOnly, boolean isEraser) { area = new Area(((ShapeDrawable) drawable).getShape()); } } - if (isEraser) { - renderer.getZone().removeTopology(area); - MapTool.serverCommand() - .removeTopology(renderer.getZone().getId(), area, renderer.getZone().getTopologyTypes()); - } else { - renderer.getZone().addTopology(area); - MapTool.serverCommand() - .addTopology(renderer.getZone().getId(), area, renderer.getZone().getTopologyTypes()); - } - renderer.repaint(); + + MapTool.serverCommand() + .updateTopology(renderer.getZone(), area, isEraser, renderer.getZone().getTopologyTypes()); } private Path2D getPath(Drawable drawable) { diff --git a/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/EditTokenDialog.java b/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/EditTokenDialog.java index 390a3587a3..aece991381 100644 --- a/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/EditTokenDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/EditTokenDialog.java @@ -1343,11 +1343,12 @@ public void initTokenTopologyPanel() { for (final var type : Zone.TopologyType.values()) { final var topology = getTokenTopologyPanel().getTopology(type); if (topology != null) { - TokenVBL.renderTopology( - MapTool.getFrame().getCurrentZoneRenderer(), - getTokenTopologyPanel().getToken().getTransformedTopology(topology), - false, - type); + MapTool.serverCommand() + .updateTopology( + MapTool.getFrame().getCurrentZoneRenderer().getZone(), + getTokenTopologyPanel().getToken().getTransformedTopology(topology), + false, + type); } } @@ -1377,8 +1378,12 @@ public void initTokenTopologyPanel() { MapTool.getFrame().getCurrentZoneRenderer(), getTokenTopologyPanel().getToken(), type); - TokenVBL.renderTopology( - MapTool.getFrame().getCurrentZoneRenderer(), topologyToDelete, true, type); + MapTool.serverCommand() + .updateTopology( + MapTool.getFrame().getCurrentZoneRenderer().getZone(), + topologyToDelete, + true, + type); } } diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java b/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java index b282f248c7..2c9ef9173a 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java @@ -26,7 +26,6 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; -import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.swing.SwingUtil; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; import net.rptools.maptool.language.I18N; @@ -177,28 +176,6 @@ public static Area simplifyArea( return simplifiedArea; } - /** - * This is a convenience method to send the topology Area to be rendered to the server - * - * @param renderer Reference to the ZoneRenderer - * @param area A valid Area containing topology polygons - * @param erase Set to true to erase the topology, otherwise draw it - * @param topologyType Determines which topology (Wall VBL, Hill VBL, Pit VBL, MBL) to modify. - */ - public static void renderTopology( - ZoneRenderer renderer, Area area, boolean erase, Zone.TopologyType topologyType) { - if (erase) { - renderer.getZone().removeTopology(area, topologyType); - MapTool.serverCommand().removeTopology(renderer.getZone().getId(), area, topologyType); - } else { - renderer.getZone().addTopology(area, topologyType); - MapTool.serverCommand().addTopology(renderer.getZone().getId(), area, topologyType); - } - - MapTool.getFrame().getCurrentZoneRenderer().getZone().tokenTopologyChanged(); - renderer.repaint(); - } - public static Area getMapTopology_transformed( ZoneRenderer renderer, Token token, Zone.TopologyType topologyType) { Rectangle footprintBounds = token.getBounds(renderer.getZone()); diff --git a/src/main/java/net/rptools/maptool/client/utilities/DungeonDraftImporter.java b/src/main/java/net/rptools/maptool/client/utilities/DungeonDraftImporter.java index ea940a5ab6..6dbf648826 100644 --- a/src/main/java/net/rptools/maptool/client/utilities/DungeonDraftImporter.java +++ b/src/main/java/net/rptools/maptool/client/utilities/DungeonDraftImporter.java @@ -203,8 +203,8 @@ public void importVTT() throws IOException { WALL_VBL_STROKE.createStrokedShape( getVBLPath(v.getAsJsonArray(), pixelsPerCell))); if (finalDo_transform) vblArea.transform(at); - zone.addTopology(vblArea, Zone.TopologyType.WALL_VBL); - zone.addTopology(vblArea, Zone.TopologyType.MBL); + zone.updateTopology(vblArea, true, Zone.TopologyType.WALL_VBL); + zone.updateTopology(vblArea, true, Zone.TopologyType.MBL); }); } @@ -215,8 +215,8 @@ public void importVTT() throws IOException { v -> { Area vblArea = new Area(getVBLPath(v.getAsJsonArray(), pixelsPerCell)); if (finalDo_transform) vblArea.transform(at); - zone.addTopology(vblArea, Zone.TopologyType.HILL_VBL); - zone.addTopology(vblArea, Zone.TopologyType.PIT_VBL); + zone.updateTopology(vblArea, true, Zone.TopologyType.HILL_VBL); + zone.updateTopology(vblArea, true, Zone.TopologyType.PIT_VBL); }); } @@ -239,8 +239,8 @@ public void importVTT() throws IOException { Area vblArea = new Area(DOOR_VBL_STROKE.createStrokedShape(getVBLPath(bounds, pixelsPerCell))); if (finalDo_transform) vblArea.transform(at); - zone.addTopology(vblArea, Zone.TopologyType.WALL_VBL); - zone.addTopology(vblArea, Zone.TopologyType.MBL); + zone.updateTopology(vblArea, true, Zone.TopologyType.WALL_VBL); + zone.updateTopology(vblArea, true, Zone.TopologyType.MBL); } }); } diff --git a/src/main/java/net/rptools/maptool/model/Zone.java b/src/main/java/net/rptools/maptool/model/Zone.java index 3bd4ac6714..caf86964c1 100644 --- a/src/main/java/net/rptools/maptool/model/Zone.java +++ b/src/main/java/net/rptools/maptool/model/Zone.java @@ -1013,7 +1013,7 @@ public Area getTopology(TopologyType topologyType) { * @param area the area * @param topologyType the type of the topology */ - public void addTopology(Area area, TopologyType topologyType) { + public void updateTopology(Area area, boolean erase, TopologyType topologyType) { var topology = switch (topologyType) { case WALL_VBL -> this.topology; @@ -1022,43 +1022,16 @@ public void addTopology(Area area, TopologyType topologyType) { case COVER_VBL -> coverVbl; case MBL -> topologyTerrain; }; - topology.add(area); - new MapToolEventBus().getMainEventBus().post(new TopologyChanged(this)); - } - - public void addTopology(Area area) { - for (var topologyType : getTopologyTypes()) { - addTopology(area, topologyType); + if (erase) { + topology.subtract(area); + } else { + topology.add(area); } - } - - /** - * Subtract the area from the topology, and fire the event TOPOLOGY_CHANGED - * - * @param area the area - * @param topologyType the type of the topology - */ - public void removeTopology(Area area, TopologyType topologyType) { - var topology = - switch (topologyType) { - case WALL_VBL -> this.topology; - case HILL_VBL -> hillVbl; - case PIT_VBL -> pitVbl; - case COVER_VBL -> coverVbl; - case MBL -> topologyTerrain; - }; - topology.subtract(area); new MapToolEventBus().getMainEventBus().post(new TopologyChanged(this)); } - public void removeTopology(Area area) { - for (var topologyType : getTopologyTypes()) { - removeTopology(area, topologyType); - } - } - /** Fire the event TOPOLOGY_CHANGED. */ // TODO Remove this in favour of firing from token as it own its topology. public void tokenTopologyChanged() { diff --git a/src/main/java/net/rptools/maptool/server/ServerCommand.java b/src/main/java/net/rptools/maptool/server/ServerCommand.java index 7edec64833..17acbb5564 100644 --- a/src/main/java/net/rptools/maptool/server/ServerCommand.java +++ b/src/main/java/net/rptools/maptool/server/ServerCommand.java @@ -41,21 +41,14 @@ public interface ServerCommand { void setFoW(GUID zoneGUID, Area area, Set selectedToks); - default void addTopology(GUID zoneGUID, Area area, Zone.TopologyTypeSet topologyTypes) { + default void updateTopology( + Zone zone, Area area, boolean erase, Zone.TopologyTypeSet topologyTypes) { for (var topologyType : topologyTypes) { - addTopology(zoneGUID, area, topologyType); + updateTopology(zone, area, erase, topologyType); } } - void addTopology(GUID zoneGUID, Area area, Zone.TopologyType topologyType); - - default void removeTopology(GUID zoneGUID, Area area, Zone.TopologyTypeSet topologyTypes) { - for (var topologyType : topologyTypes) { - removeTopology(zoneGUID, area, topologyType); - } - } - - void removeTopology(GUID zoneGUID, Area area, Zone.TopologyType topologyType); + void updateTopology(Zone zone, Area area, boolean erase, Zone.TopologyType topologyType); void enforceZoneView(GUID zoneGUID, int x, int y, double scale, int width, int height); diff --git a/src/main/java/net/rptools/maptool/server/ServerMessageHandler.java b/src/main/java/net/rptools/maptool/server/ServerMessageHandler.java index df66daae4c..82e964a949 100644 --- a/src/main/java/net/rptools/maptool/server/ServerMessageHandler.java +++ b/src/main/java/net/rptools/maptool/server/ServerMessageHandler.java @@ -78,8 +78,8 @@ public void handleMessage(String id, byte[] message) { } switch (msgType) { - case ADD_TOPOLOGY_MSG -> { - handle(msg.getAddTopologyMsg()); + case UPDATE_TOPOLOGY_MSG -> { + handle(msg.getUpdateTopologyMsg()); sendToClients(id, msg); } case BRING_TOKENS_TO_FRONT_MSG -> handle(msg.getBringTokensToFrontMsg()); @@ -171,10 +171,6 @@ public void handleMessage(String id, byte[] message) { handle(msg.getRemoveTokensMsg()); sendToClients(id, msg); } - case REMOVE_TOPOLOGY_MSG -> { - handle(msg.getRemoveTopologyMsg()); - sendToClients(id, msg); - } case REMOVE_ZONE_MSG -> { handle(msg.getRemoveZoneMsg()); sendToClients(id, msg); @@ -493,17 +489,6 @@ private void handle(RemoveZoneMsg msg) { }); } - private void handle(RemoveTopologyMsg msg) { - EventQueue.invokeLater( - () -> { - var zoneGUID = GUID.valueOf(msg.getZoneGuid()); - var area = Mapper.map(msg.getArea()); - var topologyType = Zone.TopologyType.valueOf(msg.getType().name()); - Zone zone = server.getCampaign().getZone(zoneGUID); - zone.removeTopology(area, topologyType); - }); - } - private void handle(RemoveTokensMsg msg) { EventQueue.invokeLater( () -> { @@ -677,14 +662,15 @@ private void handle(BringTokensToFrontMsg bringTokensToFrontMsg) { }); } - private void handle(AddTopologyMsg addTopologyMsg) { + private void handle(UpdateTopologyMsg updateTopologyMsg) { EventQueue.invokeLater( () -> { - var zoneGUID = GUID.valueOf(addTopologyMsg.getZoneGuid()); - var area = Mapper.map(addTopologyMsg.getArea()); - var topologyType = Zone.TopologyType.valueOf(addTopologyMsg.getType().name()); + var zoneGUID = GUID.valueOf(updateTopologyMsg.getZoneGuid()); + var area = Mapper.map(updateTopologyMsg.getArea()); + var erase = updateTopologyMsg.getErase(); + var topologyType = Zone.TopologyType.valueOf(updateTopologyMsg.getType().name()); Zone zone = server.getCampaign().getZone(zoneGUID); - zone.addTopology(area, topologyType); + zone.updateTopology(area, erase, topologyType); }); } diff --git a/src/main/proto/message.proto b/src/main/proto/message.proto index 5deea9c19c..c0fa398fc2 100644 --- a/src/main/proto/message.proto +++ b/src/main/proto/message.proto @@ -15,7 +15,7 @@ import "message_types.proto"; message Message { oneof message_type { - AddTopologyMsg add_topology_msg = 1; + UpdateTopologyMsg update_topology_msg = 501; BootPlayerMsg boot_player_msg = 2; BringTokensToFrontMsg bring_tokens_to_front_msg = 3; ChangeZoneDisplayNameMsg change_zone_display_name_msg = 4; @@ -47,7 +47,6 @@ message Message { RemoveLabelMsg remove_label_msg = 30; RemoveTokenMsg remove_token_msg = 31; RemoveTokensMsg remove_tokens_msg = 32; - RemoveTopologyMsg remove_topology_msg = 33; RemoveZoneMsg remove_zone_msg = 34; RenameZoneMsg rename_zone_msg = 35; RestoreZoneViewMsg restore_zone_view_msg = 36; diff --git a/src/main/proto/message_types.proto b/src/main/proto/message_types.proto index d2e4c92345..931e785e43 100644 --- a/src/main/proto/message_types.proto +++ b/src/main/proto/message_types.proto @@ -16,10 +16,11 @@ import "data_transfer_objects.proto"; import "drawing_dto.proto"; import "gamedata.proto"; -message AddTopologyMsg { +message UpdateTopologyMsg { string zone_guid = 1; AreaDto area = 2; - TopologyTypeDto type = 3; + bool erase = 3; + TopologyTypeDto type = 4; } message BootPlayerMsg { @@ -174,12 +175,6 @@ message RemoveTokensMsg { repeated string token_guid = 2; } -message RemoveTopologyMsg { - string zone_guid = 1; - AreaDto area = 2; - TopologyTypeDto type = 3; -} - message RemoveZoneMsg { string zone_guid = 1; } From b32e0ad950e03e45d7271ee9dad400ae01e6497b Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Wed, 23 Oct 2024 17:18:49 -0700 Subject: [PATCH 129/146] Perform token topology edits through server commands There's only a couple places that need to do it, but both of them forgot to sync to remote clients. --- .../client/ServerCommandClientImpl.java | 19 +++++++++---------- .../client/functions/Topology_Functions.java | 12 +++++++----- .../java/net/rptools/maptool/model/Token.java | 9 +++++---- .../rptools/maptool/server/ServerCommand.java | 6 +++--- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java b/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java index ec83158f01..e24d742a7c 100644 --- a/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java +++ b/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java @@ -618,6 +618,15 @@ public void removeData(String type, String namespace, String name) { makeServerCall(Message.newBuilder().setRemoveDataMsg(msg).build()); } + @Override + public void setTokenTopology(Token token, Area area, Zone.TopologyType topologyType) { + updateTokenProperty( + token, + Token.Update.setTopology, + TokenPropertyValueDto.newBuilder().setTopologyType(topologyType.name()).build(), + TokenPropertyValueDto.newBuilder().setArea(Mapper.map(area)).build()); + } + @Override public void updateTokenProperty(Token token, Token.Update update, int value) { updateTokenProperty( @@ -752,16 +761,6 @@ public void updateTokenProperty( TokenPropertyValueDto.newBuilder().setIntValue(value3).build()); } - @Override - public void updateTokenProperty( - Token token, Token.Update update, Zone.TopologyType topologyType, Area area) { - updateTokenProperty( - token, - update, - TokenPropertyValueDto.newBuilder().setTopologyType(topologyType.name()).build(), - TokenPropertyValueDto.newBuilder().setArea(Mapper.map(area)).build()); - } - @Override public void updateTokenProperty(Token token, Token.Update update, String value1, boolean value2) { updateTokenProperty( diff --git a/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java b/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java index 12b58b972f..bddd5e2e13 100644 --- a/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java +++ b/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java @@ -515,8 +515,7 @@ private int childEvaluateSetTokenTopology( } } // Replace with new topology - MapTool.serverCommand() - .updateTokenProperty(token, Token.Update.setTopology, topologyType, tokenTopology); + MapTool.serverCommand().setTokenTopology(token, tokenTopology, topologyType); return results; } @@ -599,12 +598,15 @@ private void childEvaluateTransferTopology( .updateTopology( renderer.getZone(), token.getTransformedTopology(topologyType), false, topologyType); if (delete) { - token.setTopology(topologyType, null); + MapTool.serverCommand().setTokenTopology(token, null, topologyType); } } else { Area topology = TokenVBL.getTopology_underToken(renderer, token, topologyType); - token.setTopology( - topologyType, TokenVBL.getMapTopology_transformed(renderer, token, topologyType)); + MapTool.serverCommand() + .setTokenTopology( + token, + TokenVBL.getMapTopology_transformed(renderer, token, topologyType), + topologyType); if (delete) { MapTool.serverCommand().updateTopology(renderer.getZone(), topology, true, topologyType); } diff --git a/src/main/java/net/rptools/maptool/model/Token.java b/src/main/java/net/rptools/maptool/model/Token.java index 7b9b94cab5..c2c9193b76 100644 --- a/src/main/java/net/rptools/maptool/model/Token.java +++ b/src/main/java/net/rptools/maptool/model/Token.java @@ -2651,6 +2651,7 @@ public void updateProperty(Zone zone, Update update, List boolean lightChanged = false; boolean macroChanged = false; boolean panelLookChanged = false; // appearance of token in a panel changed + boolean topologyChanged = false; switch (update) { case setState: var state = parameters.get(0).getStringValue(); @@ -2817,10 +2818,7 @@ public void updateProperty(Zone zone, Update update, List { final var topologyType = Zone.TopologyType.valueOf(parameters.get(0).getTopologyType()); setTopology(topologyType, Mapper.map(parameters.get(1).getArea())); - if (!hasTopology(topologyType)) { // if topology removed - zone.tokenTopologyChanged(); // if token lost topology, TOKEN_CHANGED won't update - // topology - } + topologyChanged = true; break; } case setImageAsset: @@ -2910,6 +2908,9 @@ public void updateProperty(Zone zone, Update update, List if (panelLookChanged) { zone.tokenPanelChanged(this); } + if (topologyChanged) { + zone.tokenTopologyChanged(); + } zone.tokenChanged(this); // fire Event.TOKEN_CHANGED, which updates topology if token has VBL } diff --git a/src/main/java/net/rptools/maptool/server/ServerCommand.java b/src/main/java/net/rptools/maptool/server/ServerCommand.java index 17acbb5564..eb647f1f46 100644 --- a/src/main/java/net/rptools/maptool/server/ServerCommand.java +++ b/src/main/java/net/rptools/maptool/server/ServerCommand.java @@ -18,6 +18,7 @@ import java.math.BigDecimal; import java.util.List; import java.util.Set; +import javax.annotation.Nullable; import net.rptools.lib.MD5Key; import net.rptools.maptool.model.*; import net.rptools.maptool.model.Zone.VisionType; @@ -182,6 +183,8 @@ default void updateTopology( void removeData(String type, String namespace, String name); + void setTokenTopology(Token token, @Nullable Area area, Zone.TopologyType topologyType); + void updateTokenProperty(Token token, Token.Update update, int value); void updateTokenProperty(Token token, Token.Update update, String value1, String value2); @@ -214,9 +217,6 @@ void updateTokenProperty( void updateTokenProperty( Token token, Token.Update update, boolean value1, int value2, int value3); - void updateTokenProperty( - Token token, Token.Update update, Zone.TopologyType topologyType, Area area); - void updateTokenProperty(Token token, Token.Update update, String value1, boolean value2); void updateTokenProperty(Token token, Token.Update update, String value, BigDecimal value2); From 2173623401c22d471b3ebefede220d2dafa5abd2 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Wed, 23 Oct 2024 17:48:36 -0700 Subject: [PATCH 130/146] Handle transferring null token topology The way to get rid of topology from a token is to set it to `null`. This change allows it to actually be sent over the wire, in the form of an empty area. --- .../rptools/maptool/client/ServerCommandClientImpl.java | 8 +++++++- .../maptool/client/functions/Topology_Functions.java | 8 +++++--- .../net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java | 3 ++- src/main/java/net/rptools/maptool/model/Token.java | 4 ++++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java b/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java index e24d742a7c..bdcf998b22 100644 --- a/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java +++ b/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import javax.annotation.Nullable; import net.rptools.lib.MD5Key; import net.rptools.maptool.client.functions.ExecFunction; import net.rptools.maptool.client.functions.MacroLinkFunction; @@ -619,7 +620,12 @@ public void removeData(String type, String namespace, String name) { } @Override - public void setTokenTopology(Token token, Area area, Zone.TopologyType topologyType) { + public void setTokenTopology(Token token, @Nullable Area area, Zone.TopologyType topologyType) { + if (area == null) { + // Will be converted back to null on the other end. + area = new Area(); + } + updateTokenProperty( token, Token.Update.setTopology, diff --git a/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java b/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java index bddd5e2e13..8b5555a383 100644 --- a/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java +++ b/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java @@ -594,9 +594,11 @@ private void childEvaluateTransferTopology( } if (topologyFromToken) { - MapTool.serverCommand() - .updateTopology( - renderer.getZone(), token.getTransformedTopology(topologyType), false, topologyType); + var newMapTopology = token.getTransformedTopology(topologyType); + if (newMapTopology != null) { + MapTool.serverCommand() + .updateTopology(renderer.getZone(), newMapTopology, false, topologyType); + } if (delete) { MapTool.serverCommand().setTokenTopology(token, null, topologyType); } diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java b/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java index 2c9ef9173a..416a44f5cb 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; +import javax.annotation.Nonnull; import net.rptools.maptool.client.swing.SwingUtil; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; import net.rptools.maptool.language.I18N; @@ -254,7 +255,7 @@ public static Area getMapTopology_transformed( return newTokenTopology; } - public static Area getTopology_underToken( + public static @Nonnull Area getTopology_underToken( ZoneRenderer renderer, Token token, Zone.TopologyType topologyType) { Rectangle footprintBounds = token.getBounds(renderer.getZone()); Area newTokenTopology = new Area(footprintBounds); diff --git a/src/main/java/net/rptools/maptool/model/Token.java b/src/main/java/net/rptools/maptool/model/Token.java index c2c9193b76..fbb392e64d 100644 --- a/src/main/java/net/rptools/maptool/model/Token.java +++ b/src/main/java/net/rptools/maptool/model/Token.java @@ -1399,6 +1399,10 @@ public Area getTransformedTopology(Zone.TopologyType topologyType) { * @param topology the topology area to set. */ public void setTopology(Zone.TopologyType topologyType, @Nullable Area topology) { + if (topology != null && topology.isEmpty()) { + topology = null; + } + switch (topologyType) { case WALL_VBL -> vbl = topology; case HILL_VBL -> hillVbl = topology; From 337e332852f472289b6e8db25c67af4332bf68e5 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Wed, 23 Oct 2024 17:53:35 -0700 Subject: [PATCH 131/146] Refactor TokenVBL.getTopology_underToken() The results of a few calculations were not used, so a good chunk of the method could just be deleted. Beyond that, the method was written to be as condensed as possible, and avoiding unnecessary `Area` copies. The method `TokenVBL.getMapTopology_transformed()` was removed because it had to do everything that `TokenVBL.getTopology_underToken()` plus transforming it back into token space. A new method `TokenVBL.transformTopology_toToken()` takes its place, and can transform an `Area` (no matter the source) into token space. This process was taken from `TokenVBL.getMapTopology_transformed()`, but cleaned up to produce a single transformation and avoid any intermediate `Area`. --- .../client/functions/Topology_Functions.java | 14 +- .../ui/token/dialog/edit/EditTokenDialog.java | 24 +-- .../maptool/client/ui/zone/vbl/TokenVBL.java | 142 ++++++------------ 3 files changed, 56 insertions(+), 124 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java b/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java index 8b5555a383..a7e208d2f5 100644 --- a/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java +++ b/src/main/java/net/rptools/maptool/client/functions/Topology_Functions.java @@ -523,7 +523,6 @@ private int childEvaluateSetTokenTopology( private void childEvaluateTransferTopology( VariableResolver resolver, String functionName, List parameters) throws ParserException { - ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); Token token = null; Zone.TopologyType topologyType; @@ -593,24 +592,23 @@ private void childEvaluateTransferTopology( } } + Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); if (topologyFromToken) { var newMapTopology = token.getTransformedTopology(topologyType); if (newMapTopology != null) { - MapTool.serverCommand() - .updateTopology(renderer.getZone(), newMapTopology, false, topologyType); + MapTool.serverCommand().updateTopology(zone, newMapTopology, false, topologyType); } if (delete) { MapTool.serverCommand().setTokenTopology(token, null, topologyType); } } else { - Area topology = TokenVBL.getTopology_underToken(renderer, token, topologyType); + Area topology = TokenVBL.getTopology_underToken(zone, token, topologyType); + MapTool.serverCommand() .setTokenTopology( - token, - TokenVBL.getMapTopology_transformed(renderer, token, topologyType), - topologyType); + token, TokenVBL.transformTopology_toToken(zone, token, topology), topologyType); if (delete) { - MapTool.serverCommand().updateTopology(renderer.getZone(), topology, true, topologyType); + MapTool.serverCommand().updateTopology(zone, topology, true, topologyType); } } } diff --git a/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/EditTokenDialog.java b/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/EditTokenDialog.java index aece991381..6f367aa107 100644 --- a/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/EditTokenDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/EditTokenDialog.java @@ -1362,28 +1362,18 @@ public void initTokenTopologyPanel() { getTransferTopologyFromMap() .addActionListener( e -> { + var zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); + var token = getTokenTopologyPanel().getToken(); final boolean removeFromMap = getCopyOrMoveCheckbox().isSelected(); for (final var type : getTokenTopologyPanel().getSelectedTopologyTypes()) { - Area mapTopology = - TokenVBL.getMapTopology_transformed( - MapTool.getFrame().getCurrentZoneRenderer(), - getTokenTopologyPanel().getToken(), - type); + Area mapTopology = TokenVBL.getTopology_underToken(zone, token, type); + Area newTokenTopology = + TokenVBL.transformTopology_toToken(zone, token, mapTopology); - getTokenTopologyPanel().putCustomTopology(type, mapTopology); + getTokenTopologyPanel().putCustomTopology(type, newTokenTopology); if (removeFromMap) { - Area topologyToDelete = - TokenVBL.getTopology_underToken( - MapTool.getFrame().getCurrentZoneRenderer(), - getTokenTopologyPanel().getToken(), - type); - MapTool.serverCommand() - .updateTopology( - MapTool.getFrame().getCurrentZoneRenderer().getZone(), - topologyToDelete, - true, - type); + MapTool.serverCommand().updateTopology(zone, mapTopology, true, type); } } diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java b/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java index 416a44f5cb..9a5fc61d34 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java @@ -26,9 +26,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; -import javax.annotation.Nonnull; import net.rptools.maptool.client.swing.SwingUtil; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.Token; import net.rptools.maptool.model.Zone; @@ -177,124 +175,70 @@ public static Area simplifyArea( return simplifiedArea; } - public static Area getMapTopology_transformed( - ZoneRenderer renderer, Token token, Zone.TopologyType topologyType) { - Rectangle footprintBounds = token.getBounds(renderer.getZone()); - Area newTokenTopology = new Area(footprintBounds); - Dimension imgSize = new Dimension(token.getWidth(), token.getHeight()); - SwingUtil.constrainTo(imgSize, footprintBounds.width, footprintBounds.height); - AffineTransform atArea = new AffineTransform(); - - double tx, ty, sx, sy; - - // Prepare to reverse all the current token transformations so we can store a - // raw untransformed version on the Token - if (token.isSnapToScale()) { - tx = - -newTokenTopology.getBounds().getX() - - (int) ((footprintBounds.getWidth() - imgSize.getWidth()) / 2); - ty = - -newTokenTopology.getBounds().getY() - - (int) ((footprintBounds.getHeight() - imgSize.getHeight()) / 2); - sx = 1 / (imgSize.getWidth() / token.getWidth()); - sy = 1 / (imgSize.getHeight() / token.getHeight()); - - } else { - tx = -newTokenTopology.getBounds().getX(); - ty = -newTokenTopology.getBounds().getY(); - sx = 1 / token.getScaleX(); - sy = 1 / token.getScaleY(); - } + public static Area getTopology_underToken( + Zone zone, Token token, Zone.TopologyType topologyType) { + Area topologyOnMap = zone.getTopology(topologyType); + Rectangle footprintBounds = token.getBounds(zone); - atArea.concatenate(AffineTransform.getScaleInstance(sx, sy)); - - Area mapArea = renderer.getZone().getTopology(topologyType); - - if (token.getShape() == Token.TokenShape.TOP_DOWN - && Math.toRadians(token.getFacingInDegrees()) != 0.0) { + final AffineTransform captureArea = new AffineTransform(); + final Area topologyUnderToken; + if (token.getShape() == Token.TokenShape.TOP_DOWN && token.getFacingInDegrees() != 0) { // Get the center of the token bounds - double rx = newTokenTopology.getBounds2D().getCenterX(); - double ry = newTokenTopology.getBounds2D().getCenterY(); + // TODO Incorporate the anchor. + double rx = footprintBounds.getCenterX(); + double ry = footprintBounds.getCenterY(); // Rotate the area to match the token facing - AffineTransform captureArea = - AffineTransform.getRotateInstance(Math.toRadians(token.getFacingInDegrees()), rx, ry); - newTokenTopology = new Area(captureArea.createTransformedShape(newTokenTopology)); - - // Capture the topology via intersection - newTokenTopology.intersect(mapArea); - - // Rotate the area back to prep to store on Token - captureArea = - AffineTransform.getRotateInstance(-Math.toRadians(token.getFacingInDegrees()), rx, ry); - newTokenTopology = new Area(captureArea.createTransformedShape(newTokenTopology)); + captureArea.rotate(Math.toRadians(token.getFacingInDegrees()), rx, ry); + topologyUnderToken = new Area(captureArea.createTransformedShape(footprintBounds)); } else { - // Token will not be rotated so lets just capture the topology - newTokenTopology.intersect(mapArea); + topologyUnderToken = new Area(footprintBounds); } - // Translate the capture to zero out the x,y to store on the Token - atArea.concatenate(AffineTransform.getTranslateInstance(tx, ty)); - newTokenTopology = new Area(atArea.createTransformedShape(newTokenTopology)); + // Capture the topology via intersection + topologyUnderToken.intersect(topologyOnMap); + return topologyUnderToken; + } + + public static Area transformTopology_toToken(Zone zone, Token token, Area topologyUnderToken) { + Rectangle footprintBounds = token.getBounds(zone); - // Lets account for flipped images... - atArea = new AffineTransform(); + // Reverse all token transformations so we can store a raw untransformed version on the Token. + AffineTransform atArea = new AffineTransform(); + // Let's account for flipped images... if (token.isFlippedX()) { - atArea.concatenate(AffineTransform.getScaleInstance(-1.0, 1.0)); - atArea.concatenate(AffineTransform.getTranslateInstance(-token.getWidth(), 0)); + atArea.scale(-1, 1); + atArea.translate(-token.getWidth(), 0); } - if (token.isFlippedY()) { - atArea.concatenate(AffineTransform.getScaleInstance(1.0, -1.0)); - atArea.concatenate(AffineTransform.getTranslateInstance(0, -token.getHeight())); + atArea.scale(1, -1); + atArea.translate(0, -token.getHeight()); } - - // Do any final transformations for flipped images - newTokenTopology = new Area(atArea.createTransformedShape(newTokenTopology)); - - return newTokenTopology; - } - - public static @Nonnull Area getTopology_underToken( - ZoneRenderer renderer, Token token, Zone.TopologyType topologyType) { - Rectangle footprintBounds = token.getBounds(renderer.getZone()); - Area newTokenTopology = new Area(footprintBounds); - Dimension imgSize = new Dimension(token.getWidth(), token.getHeight()); - SwingUtil.constrainTo(imgSize, footprintBounds.width, footprintBounds.height); - AffineTransform atArea = new AffineTransform(); - - double sx, sy; - Area topologyOnMap = renderer.getZone().getTopology(topologyType); - + // Translate the capture to zero out the x,y to store on the Token if (token.isSnapToScale()) { - sx = 1 / (imgSize.getWidth() / token.getWidth()); - sy = 1 / (imgSize.getHeight() / token.getHeight()); - + Dimension imgSize = new Dimension(token.getWidth(), token.getHeight()); + SwingUtil.constrainTo(imgSize, footprintBounds.width, footprintBounds.height); + atArea.scale(token.getWidth() / imgSize.getWidth(), token.getHeight() / imgSize.getHeight()); + atArea.translate( + -footprintBounds.getX() - (int) ((footprintBounds.getWidth() - imgSize.getWidth()) / 2), + -footprintBounds.getY() + - (int) ((footprintBounds.getHeight() - imgSize.getHeight()) / 2)); } else { - sx = 1 / token.getScaleX(); - sy = 1 / token.getScaleY(); + atArea.scale(1 / token.getScaleX(), 1 / token.getScaleY()); + atArea.translate(-footprintBounds.getX(), -footprintBounds.getY()); } - atArea.concatenate(AffineTransform.getScaleInstance(sx, sy)); - if (token.getShape() == Token.TokenShape.TOP_DOWN - && Math.toRadians(token.getFacingInDegrees()) != 0.0) { + if (token.getShape() == Token.TokenShape.TOP_DOWN && token.getFacingInDegrees() != 0) { // Get the center of the token bounds - double rx = newTokenTopology.getBounds2D().getCenterX(); - double ry = newTokenTopology.getBounds2D().getCenterY(); + // TODO Incorporate the anchor. + double rx = footprintBounds.getCenterX(); + double ry = footprintBounds.getCenterY(); // Rotate the area to match the token facing - AffineTransform captureArea = - AffineTransform.getRotateInstance(Math.toRadians(token.getFacingInDegrees()), rx, ry); - newTokenTopology = new Area(captureArea.createTransformedShape(newTokenTopology)); - - // Capture the topology via intersection - newTokenTopology.intersect(topologyOnMap); - } else { - // Token will not be rotated so lets just capture the topology - newTokenTopology.intersect(topologyOnMap); + atArea.rotate(-Math.toRadians(token.getFacingInDegrees()), rx, ry); } - return newTokenTopology; + return new Area(atArea.createTransformedShape(topologyUnderToken)); } /** From b96eb6f1cbb186d7f232a551e6c7770f9135054e Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Wed, 23 Oct 2024 22:52:46 -0700 Subject: [PATCH 132/146] Fix TokenVBL rotation by incorporating the token anchor --- .../rptools/maptool/client/ui/zone/vbl/TokenVBL.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java b/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java index 9a5fc61d34..8395cb179d 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/vbl/TokenVBL.java @@ -184,9 +184,8 @@ public static Area getTopology_underToken( final Area topologyUnderToken; if (token.getShape() == Token.TokenShape.TOP_DOWN && token.getFacingInDegrees() != 0) { // Get the center of the token bounds - // TODO Incorporate the anchor. - double rx = footprintBounds.getCenterX(); - double ry = footprintBounds.getCenterY(); + double rx = footprintBounds.getCenterX() - token.getAnchor().x; + double ry = footprintBounds.getCenterY() - token.getAnchor().y; // Rotate the area to match the token facing captureArea.rotate(Math.toRadians(token.getFacingInDegrees()), rx, ry); @@ -230,9 +229,8 @@ public static Area transformTopology_toToken(Zone zone, Token token, Area topolo if (token.getShape() == Token.TokenShape.TOP_DOWN && token.getFacingInDegrees() != 0) { // Get the center of the token bounds - // TODO Incorporate the anchor. - double rx = footprintBounds.getCenterX(); - double ry = footprintBounds.getCenterY(); + double rx = footprintBounds.getCenterX() - token.getAnchor().x; + double ry = footprintBounds.getCenterY() - token.getAnchor().y; // Rotate the area to match the token facing atArea.rotate(-Math.toRadians(token.getFacingInDegrees()), rx, ry); From 1e6df323c91ee469a3abe8a847b9eb526d84f9c9 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 15:33:35 -0700 Subject: [PATCH 133/146] Fix Oval.toDto() so the layer is set --- src/main/java/net/rptools/maptool/model/drawing/Oval.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/net/rptools/maptool/model/drawing/Oval.java b/src/main/java/net/rptools/maptool/model/drawing/Oval.java index 9123ed0c9f..4b16a8425f 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/Oval.java +++ b/src/main/java/net/rptools/maptool/model/drawing/Oval.java @@ -82,6 +82,7 @@ public DrawableDto toDto() { var dto = OvalDrawableDto.newBuilder() .setId(getId().toString()) + .setLayer(getLayer().name()) .setStartPoint(Mapper.map(getStartPoint())) .setEndPoint(Mapper.map(getEndPoint())); From b08cb7707841478fceac0f890cd14a7cdfa15e7b Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Fri, 18 Oct 2024 02:18:27 -0700 Subject: [PATCH 134/146] Repaint ZoneRenderer when overlays are added or removed --- .../rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java index 0bf71db39a..2e2690e862 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java @@ -654,10 +654,12 @@ public Zone getZone() { public void addOverlay(ZoneOverlay overlay) { overlayList.add(overlay); + repaintDebouncer.dispatch(); } public void removeOverlay(ZoneOverlay overlay) { overlayList.remove(overlay); + repaintDebouncer.dispatch(); } public void moveViewBy(int dx, int dy) { From 77e4bb9a980cd1500ddc2c742b72f3fa07414212 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 14:29:47 -0700 Subject: [PATCH 135/146] Add support for instance-based tools Existing tools are all class-based, i.e., are identified by a class, and state is tracked in terms of classes. Now it is possible to define tools by passing instances. The only difference is that instance-based tools cannot be looked up by their class, but that functionality is only used for a select few transitions. --- .../rptools/maptool/client/tool/Toolbox.java | 113 +++++++++++------- .../maptool/client/ui/ToolbarPanel.java | 23 ++-- 2 files changed, 84 insertions(+), 52 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/tool/Toolbox.java b/src/main/java/net/rptools/maptool/client/tool/Toolbox.java index 9ef65a73b8..d2d79f2eb1 100644 --- a/src/main/java/net/rptools/maptool/client/tool/Toolbox.java +++ b/src/main/java/net/rptools/maptool/client/tool/Toolbox.java @@ -55,12 +55,9 @@ public Tool createTool(Class toolClass) { tool = constructor.newInstance(); // tool = constructor.newInstance((Object) null); - if (tool.hasGroup()) { - buttonGroup.add(tool); - } - + addTool(tool); toolMap.put(toolClass, tool); - tool.setToolbox(this); + return tool; } catch (InstantiationException e) { MapTool.showError(I18N.getText("msg.error.toolCannotInstantiate", toolClass.getName()), e); @@ -76,28 +73,35 @@ public Tool createTool(Class toolClass) { return null; } + /** + * Add {@code tool} to the toolbox. + * + *

This tool will not be registered by its class, so methods like {@link #getTool(Class)} will + * not be able to find it. + * + * @param tool The tool to add. + */ + public void addTool(Tool tool) { + if (tool.hasGroup()) { + buttonGroup.add(tool); + } + tool.setToolbox(this); + } + public void setTargetRenderer(final ZoneRenderer renderer) { // Need to be synchronous with the timing of the invokes within this method EventQueue.invokeLater( () -> { - final Tool oldTool = currentTool; - // Disconnect the current tool from the current renderer - setSelectedTool((Tool) null); - - // Update the renderer - EventQueue.invokeLater(() -> currentRenderer = renderer); - // Attach the old tool to the new renderer - setSelectedTool(oldTool); + detach(); + currentRenderer = renderer; + attach(); }); } public void setSelectedTool(Class toolClass) { Tool tool = toolMap.get(toolClass); - if (tool != null && tool.isAvailable()) { - tool.setSelected(true); - setSelectedTool(tool); - } + setSelectedTool(tool); } public void setSelectedTool(final Tool tool) { @@ -106,35 +110,54 @@ public void setSelectedTool(final Tool tool) { if (tool == currentTool) { return; } - if (currentTool != null && currentRenderer != null) { - currentTool.removeListeners(currentRenderer); - // currentTool.addGridBasedKeys(currentRenderer, false); - currentTool.detachFrom(currentRenderer); - - if (currentTool instanceof ZoneOverlay) { - currentRenderer.removeOverlay((ZoneOverlay) currentTool); - } - } - // Update - currentTool = tool; - - if (currentTool != null) { - if (currentRenderer != null) { - // We have a renderer at this point so we can figure out the grid type and add its - // keystrokes - // to the PointerTool. - // currentTool.addGridBasedKeys(currentRenderer, true); - currentTool.addListeners(currentRenderer); - currentTool.attachTo(currentRenderer); - - if (currentTool instanceof ZoneOverlay) { - currentRenderer.addOverlay((ZoneOverlay) currentTool); - } - } - if (MapTool.getFrame() != null) { - MapTool.getFrame().setStatusMessage(I18N.getText(currentTool.getInstructions())); - } + + detach(); + var accepted = makeCurrent(tool); + if (accepted) { + attach(); } }); } + + private void attach() { + if (currentTool != null) { + if (currentRenderer != null) { + // We have a renderer at this point so we can figure out the grid type and add its + // keystrokes to the PointerTool. + // currentTool.addGridBasedKeys(currentRenderer, true); + currentTool.addListeners(currentRenderer); + currentTool.attachTo(currentRenderer); + + if (currentTool instanceof ZoneOverlay) { + currentRenderer.addOverlay((ZoneOverlay) currentTool); + } + } + } + } + + private void detach() { + if (currentTool != null && currentRenderer != null) { + currentTool.removeListeners(currentRenderer); + // currentTool.addGridBasedKeys(currentRenderer, false); + currentTool.detachFrom(currentRenderer); + + if (currentTool instanceof ZoneOverlay) { + currentRenderer.removeOverlay((ZoneOverlay) currentTool); + } + } + } + + private boolean makeCurrent(Tool tool) { + if (tool == null || !tool.isAvailable()) { + return false; + } + + currentTool = tool; + tool.setSelected(true); + if (MapTool.getFrame() != null) { + MapTool.getFrame().setStatusMessage(I18N.getText(currentTool.getInstructions())); + } + + return true; + } } diff --git a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java index 4916c28680..418ddb28ec 100644 --- a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java @@ -469,9 +469,8 @@ public void setTokenSelectionGroupEnabled(boolean enabled) { } private class OptionPanel extends JToolBar { - - private Class firstTool; - private Class currentTool; + private Tool firstTool; + private Tool currentTool; public OptionPanel() { setFloatable(false); @@ -481,18 +480,28 @@ public OptionPanel() { } public Tool add(Class toolClass) { + final Tool tool = toolbox.createTool(toolClass); + setupTool(tool); + return tool; + } + + public Tool addTool(Tool tool) { + toolbox.addTool(tool); + setupTool(tool); + return tool; + } + + private void setupTool(Tool tool) { if (firstTool == null) { - firstTool = toolClass; + firstTool = tool; } - final Tool tool = toolbox.createTool(toolClass); tool.addActionListener( e -> { if (tool.isSelected()) { - currentTool = tool.getClass(); + currentTool = tool; } }); add(tool); - return tool; } protected void activate() { From 2fbfb589607a2178a9050ec272034bda66055349 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Sun, 20 Oct 2024 10:26:05 -0700 Subject: [PATCH 136/146] Add proper generic support to Toolbox so tools can be looked up in type-safe way --- .../net/rptools/maptool/client/tool/PointerTool.java | 2 +- .../net/rptools/maptool/client/tool/StampTool.java | 2 +- .../net/rptools/maptool/client/tool/Toolbox.java | 12 ++++++------ .../maptool/client/ui/AbstractTokenPopupMenu.java | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) 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 67724666d5..dab3c32092 100644 --- a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java @@ -879,7 +879,7 @@ public void actionPerformed(ActionEvent e) { } Toolbox toolbox = MapTool.getFrame().getToolbox(); - FacingTool tool = (FacingTool) toolbox.getTool(FacingTool.class); + FacingTool tool = toolbox.getTool(FacingTool.class); tool.init( renderer.getZone().getToken(renderer.getSelectedTokenSet().iterator().next()), renderer.getSelectedTokenSet()); diff --git a/src/main/java/net/rptools/maptool/client/tool/StampTool.java b/src/main/java/net/rptools/maptool/client/tool/StampTool.java index 03da47ed7b..74cc10bf25 100644 --- a/src/main/java/net/rptools/maptool/client/tool/StampTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/StampTool.java @@ -648,7 +648,7 @@ public void actionPerformed(ActionEvent e) { return; } Toolbox toolbox = MapTool.getFrame().getToolbox(); - FacingTool tool = (FacingTool) toolbox.getTool(FacingTool.class); + FacingTool tool = toolbox.getTool(FacingTool.class); tool.init( renderer.getZone().getToken(renderer.getSelectedTokenSet().iterator().next()), renderer.getSelectedTokenSet()); diff --git a/src/main/java/net/rptools/maptool/client/tool/Toolbox.java b/src/main/java/net/rptools/maptool/client/tool/Toolbox.java index d2d79f2eb1..9990fd65a9 100644 --- a/src/main/java/net/rptools/maptool/client/tool/Toolbox.java +++ b/src/main/java/net/rptools/maptool/client/tool/Toolbox.java @@ -44,16 +44,16 @@ public Tool getSelectedTool() { return currentTool; } - public Tool getTool(Class toolClass) { - return toolMap.get(toolClass); + @SuppressWarnings("unchecked") + public T getTool(Class toolClass) { + return (T) toolMap.get(toolClass); } - public Tool createTool(Class toolClass) { - Tool tool; + public T createTool(Class toolClass) { + T tool; try { - Constructor constructor = toolClass.getDeclaredConstructor(); + Constructor constructor = toolClass.getDeclaredConstructor(); tool = constructor.newInstance(); - // tool = constructor.newInstance((Object) null); addTool(tool); toolMap.put(toolClass, tool); diff --git a/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java b/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java index a052edcb30..c2036d646f 100644 --- a/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java +++ b/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java @@ -677,7 +677,7 @@ public SetFacingAction() { public void actionPerformed(ActionEvent e) { Toolbox toolbox = MapTool.getFrame().getToolbox(); - FacingTool tool = (FacingTool) toolbox.getTool(FacingTool.class); + FacingTool tool = toolbox.getTool(FacingTool.class); tool.init(tokenUnderMouse, renderer.getOwnedTokens(selectedTokenSet)); toolbox.setSelectedTool(FacingTool.class); From 3bbba685e05f77b4f1d217f9a79c90fd3e904fc6 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 14:34:53 -0700 Subject: [PATCH 137/146] Increase number of shapes supported by protobuf `Path2D` is now transferable in addition to to `Area`, with the latter now being implemented on top fo the former. All `Ellipse2D` are now transferable, whereas previously only `Ellipse2D.Float` was allowed. We always use double precision for ellipses. --- .../maptool/client/tool/drawing/OvalTool.java | 4 +- .../net/rptools/maptool/server/Mapper.java | 121 ++++++++++-------- src/main/proto/drawing_dto.proto | 15 ++- 3 files changed, 82 insertions(+), 58 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java index e99f99a48e..6c32668de8 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java @@ -66,7 +66,7 @@ public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { paintTransformed( g, renderer, - new ShapeDrawable(new Ellipse2D.Float(oval.x, oval.y, oval.width, oval.height)), + new ShapeDrawable(new Ellipse2D.Double(oval.x, oval.y, oval.width, oval.height)), pen); ToolHelper.drawBoxedMeasurement( @@ -101,7 +101,7 @@ public void mousePressed(MouseEvent e) { completeDrawable( getPen(), - new ShapeDrawable(new Ellipse2D.Float(oval.x, oval.y, oval.width, oval.height), true)); + new ShapeDrawable(new Ellipse2D.Double(oval.x, oval.y, oval.width, oval.height), true)); oval = null; } diff --git a/src/main/java/net/rptools/maptool/server/Mapper.java b/src/main/java/net/rptools/maptool/server/Mapper.java index 2df3670cd2..b95e1aed90 100644 --- a/src/main/java/net/rptools/maptool/server/Mapper.java +++ b/src/main/java/net/rptools/maptool/server/Mapper.java @@ -33,11 +33,55 @@ public class Mapper { /** Instance used for log messages. */ private static final Logger log = LogManager.getLogger(Mapper.class); - public static Area map(AreaDto areaDto) { - final var segments = areaDto.getSegmentsList(); - final var path = new Path2D.Double(areaDto.getWindingValue(), segments.size()); + private static PathShapeDto map(PathIterator it) { + var builder = PathShapeDto.newBuilder(); + + double[] coords = new double[6]; + builder.setWinding(PathShapeDto.WindingRule.forNumber(it.getWindingRule())); - for (final SegmentDto currentSegment : areaDto.getSegmentsList()) { + for (; !it.isDone(); it.next()) { + var segmentBuilder = SegmentDto.newBuilder(); + switch (it.currentSegment(coords)) { + case PathIterator.SEG_MOVETO -> { + var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); + var moveTo = MoveToSegment.newBuilder().setPoint0(point0Builder); + segmentBuilder.setMoveTo(moveTo); + } + case PathIterator.SEG_LINETO -> { + var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); + var lineTo = LineToSegment.newBuilder().setPoint0(point0Builder); + segmentBuilder.setLineTo(lineTo); + } + case PathIterator.SEG_QUADTO -> { + var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); + var point1Builder = DoublePointDto.newBuilder().setX(coords[2]).setY(coords[3]); + var quadTo = QuadToSegment.newBuilder().setPoint0(point0Builder).setPoint1(point1Builder); + segmentBuilder.setQuadTo(quadTo); + } + case PathIterator.SEG_CUBICTO -> { + var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); + var point1Builder = DoublePointDto.newBuilder().setX(coords[2]).setY(coords[3]); + var point2Builder = DoublePointDto.newBuilder().setX(coords[4]).setY(coords[5]); + var cubicTo = + CubicToSegment.newBuilder() + .setPoint0(point0Builder) + .setPoint1(point1Builder) + .setPoint2(point2Builder); + segmentBuilder.setCubicTo(cubicTo); + } + case PathIterator.SEG_CLOSE -> segmentBuilder.setClose(CloseSegment.newBuilder()); + } + builder.addSegments(segmentBuilder); + } + + return builder.build(); + } + + public static Path2D map(PathShapeDto pathShapeDto) { + final var segments = pathShapeDto.getSegmentsList(); + final var path = new Path2D.Double(pathShapeDto.getWindingValue(), segments.size()); + + for (final SegmentDto currentSegment : pathShapeDto.getSegmentsList()) { switch (currentSegment.getSegmentTypeCase()) { case MOVE_TO -> { final var segment = currentSegment.getMoveTo(); @@ -74,53 +118,23 @@ public static Area map(AreaDto areaDto) { } } - return new Area(path); + return path; + } + + public static PathShapeDto map(Path2D path) { + return map(path.getPathIterator(null)); + } + + public static Area map(AreaDto areaDto) { + return new Area(map(areaDto.getPath())); } public static AreaDto map(Area area) { if (area == null) return null; var builder = AreaDto.newBuilder(); - - var it = area.getPathIterator(null); - double[] coords = new double[6]; - builder.setWinding(AreaDto.WindingRule.forNumber(it.getWindingRule())); - - for (; !it.isDone(); it.next()) { - var segmentBuilder = SegmentDto.newBuilder(); - switch (it.currentSegment(coords)) { - case PathIterator.SEG_MOVETO -> { - var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); - var moveTo = MoveToSegment.newBuilder().setPoint0(point0Builder); - segmentBuilder.setMoveTo(moveTo); - } - case PathIterator.SEG_LINETO -> { - var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); - var lineTo = LineToSegment.newBuilder().setPoint0(point0Builder); - segmentBuilder.setLineTo(lineTo); - } - case PathIterator.SEG_QUADTO -> { - var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); - var point1Builder = DoublePointDto.newBuilder().setX(coords[2]).setY(coords[3]); - var quadTo = QuadToSegment.newBuilder().setPoint0(point0Builder).setPoint1(point1Builder); - segmentBuilder.setQuadTo(quadTo); - } - case PathIterator.SEG_CUBICTO -> { - var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); - var point1Builder = DoublePointDto.newBuilder().setX(coords[2]).setY(coords[3]); - var point2Builder = DoublePointDto.newBuilder().setX(coords[4]).setY(coords[5]); - var cubicTo = - CubicToSegment.newBuilder() - .setPoint0(point0Builder) - .setPoint1(point1Builder) - .setPoint2(point2Builder); - segmentBuilder.setCubicTo(cubicTo); - } - case PathIterator.SEG_CLOSE -> segmentBuilder.setClose(CloseSegment.newBuilder()); - } - builder.addSegments(segmentBuilder); - } - + var pathDto = map(area.getPathIterator(null)); + builder.setPath(pathDto); return builder.build(); } @@ -165,7 +179,10 @@ public static Shape map(ShapeDto shapeDto) { } case ELLIPSE -> { var dto = shapeDto.getEllipse(); - return new Ellipse2D.Float(dto.getX(), dto.getY(), dto.getWidth(), dto.getHeight()); + return new Ellipse2D.Double(dto.getX(), dto.getY(), dto.getWidth(), dto.getHeight()); + } + case PATH -> { + return map(shapeDto.getPath()); } default -> { log.warn("unknown ShapeDto type: " + shapeDto.getShapeTypeCase()); @@ -195,14 +212,16 @@ public static ShapeDto map(Shape shape) { dto.addPoints(pointDto); } return shapeDto.setPolygon(dto).build(); - } else if (shape instanceof Ellipse2D.Float ellipse) { + } else if (shape instanceof Ellipse2D ellipse) { var dto = EllipseDto.newBuilder() - .setX(ellipse.x) - .setY(ellipse.y) - .setWidth(ellipse.width) - .setHeight(ellipse.height); + .setX(ellipse.getX()) + .setY(ellipse.getY()) + .setWidth(ellipse.getWidth()) + .setHeight(ellipse.getHeight()); return shapeDto.setEllipse(dto).build(); + } else if (shape instanceof Path2D path) { + return shapeDto.setPath(map(path)).build(); } else { log.warn("mapping not implemented for Shape type: " + shape.getClass()); return null; diff --git a/src/main/proto/drawing_dto.proto b/src/main/proto/drawing_dto.proto index c14353a6a5..37815323de 100644 --- a/src/main/proto/drawing_dto.proto +++ b/src/main/proto/drawing_dto.proto @@ -195,10 +195,11 @@ message ShapeDto { AreaDto area = 2; PolygonDto polygon = 3; EllipseDto ellipse = 4; + PathShapeDto path = 5; } } -message AreaDto { +message PathShapeDto { enum WindingRule { EVEN_ODD = 0; NON_ZERO = 1; @@ -207,6 +208,10 @@ message AreaDto { repeated SegmentDto segments = 2; } +message AreaDto { + PathShapeDto path = 1; +} + message SegmentDto { oneof segment_type { MoveToSegment move_to = 1; @@ -262,10 +267,10 @@ message RectangleDto { } message EllipseDto { - float x = 1; - float y = 2; - float width = 3; - float height = 4; + double x = 1; + double y = 2; + double width = 3; + double height = 4; } message DrawablePaintDto { From 9a6d544ab156a063a96c2a130be3438427a7d61c Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 14:49:17 -0700 Subject: [PATCH 138/146] Shim a new base class for drawing-like tools The existing `AbstractDrawingTool` is now in a transitional state and will not exist much longer. The new-style tools will not use it, but will inherit from `AbstractDrawingLikeTool`, which provides a bare minimum of functionality. Eventually, `AbstractDrawinTool` will be repurposed into a base class for templates, and even then may not look much like it does now. --- .../tool/drawing/AbstractDrawingLikeTool.java | 63 +++++++++++++++++++ .../tool/drawing/AbstractDrawingTool.java | 36 +---------- 2 files changed, 65 insertions(+), 34 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java new file mode 100644 index 0000000000..afb6f30b69 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java @@ -0,0 +1,63 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.event.MouseEvent; +import net.rptools.maptool.client.ScreenPoint; +import net.rptools.maptool.client.swing.SwingUtil; +import net.rptools.maptool.client.tool.DefaultTool; +import net.rptools.maptool.client.ui.zone.ZoneOverlay; +import net.rptools.maptool.model.ZonePoint; + +public abstract class AbstractDrawingLikeTool extends DefaultTool implements ZoneOverlay { + private boolean isEraser; + + protected void setIsEraser(boolean eraser) { + isEraser = eraser; + } + + protected boolean isEraser() { + return isEraser; + } + + protected boolean isEraser(MouseEvent e) { + return SwingUtil.isShiftDown(e); + } + + protected boolean isSnapToGrid(MouseEvent e) { + return SwingUtil.isControlDown(e); + } + + protected boolean isSnapToCenter(MouseEvent e) { + return e.isAltDown(); + } + + protected boolean isLinearTool() { + return false; + } + + protected ZonePoint getPoint(MouseEvent e) { + ScreenPoint sp = new ScreenPoint(e.getX(), e.getY()); + ZonePoint zp = sp.convertToZoneRnd(renderer); + if (isSnapToCenter(e) && isLinearTool()) { + // Only line tools will snap to center as the Alt key for rectangle, diamond and oval + // is used for expand from center. + zp = renderer.getCellCenterAt(sp); + } else if (isSnapToGrid(e)) { + zp = renderer.getZone().getNearestVertex(zp); + } + return zp; + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java index c98aab2875..0826542b64 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java @@ -28,10 +28,8 @@ import net.rptools.maptool.client.AppStyle; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.MapToolUtil; -import net.rptools.maptool.client.ScreenPoint; import net.rptools.maptool.client.swing.SwingUtil; import net.rptools.maptool.client.swing.colorpicker.ColorPicker; -import net.rptools.maptool.client.tool.DefaultTool; import net.rptools.maptool.client.ui.zone.ZoneOverlay; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; import net.rptools.maptool.model.Token; @@ -44,11 +42,10 @@ import net.rptools.maptool.model.drawing.ShapeDrawable; /** Tool for drawing freehand lines. */ -public abstract class AbstractDrawingTool extends DefaultTool implements ZoneOverlay { +public abstract class AbstractDrawingTool extends AbstractDrawingLikeTool implements ZoneOverlay { private static final long serialVersionUID = 9121558405484986225L; - private boolean isEraser; private boolean isSnapToGridSelected; private boolean isEraseSelected; @@ -146,14 +143,6 @@ protected void detachFrom(ZoneRenderer renderer) { super.detachFrom(renderer); } - protected void setIsEraser(boolean eraser) { - isEraser = eraser; - } - - protected boolean isEraser() { - return isEraser; - } - protected boolean isBackgroundFill(MouseEvent e) { boolean defaultValue = MapTool.getFrame().getColorPicker().isFillBackgroundSelected(); return defaultValue; @@ -177,17 +166,9 @@ protected boolean isSnapToGrid(MouseEvent e) { return defaultValue; } - protected boolean isSnapToCenter(MouseEvent e) { - boolean defaultValue = false; - if (e.isAltDown()) { - defaultValue = true; - } - return defaultValue; - } - protected Pen getPen() { Pen pen = new Pen(MapTool.getFrame().getPen()); - pen.setEraser(isEraser); + pen.setEraser(isEraser()); ColorPicker picker = MapTool.getFrame().getColorPicker(); if (picker.isFillForegroundSelected()) { @@ -205,19 +186,6 @@ protected Pen getPen() { return pen; } - protected ZonePoint getPoint(MouseEvent e) { - ScreenPoint sp = new ScreenPoint(e.getX(), e.getY()); - ZonePoint zp = sp.convertToZoneRnd(renderer); - if (isSnapToCenter(e) && this instanceof AbstractLineTool) { - // Only line tools will snap to center as the Alt key for rectangle, diamond and oval - // is used for expand from center. - zp = renderer.getCellCenterAt(sp); - } else if (isSnapToGrid(e)) { - zp = renderer.getZone().getNearestVertex(zp); - } - return zp; - } - protected Area getTokenTopology(Zone.TopologyType topologyType) { List topologyTokens = getZone().getTokensWithTopology(topologyType); From f8a656b86ea51704d3569f9a7d1b372faf586fb2 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 14:55:32 -0700 Subject: [PATCH 139/146] Add support for iso measurements using points rather than Shape This will be useful in conjunction with the new strategies so that we don't have to make assumptions about the result types. --- .../maptool/client/tool/ToolHelper.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java b/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java index 2bf739db6d..06422e4b60 100644 --- a/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java +++ b/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java @@ -20,6 +20,7 @@ import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; import java.awt.geom.PathIterator; import java.text.NumberFormat; import javax.swing.AbstractAction; @@ -49,6 +50,34 @@ public void actionPerformed(ActionEvent e) { } }; + public static void drawIsoRectangleMeasurement( + ZoneRenderer renderer, Graphics2D g, ScreenPoint north, ScreenPoint west, ScreenPoint east) { + if (g != null) { + g.setColor(Color.white); + g.setStroke(new BasicStroke(3)); + g.draw(new Line2D.Double(north.x, north.y - 20, north.x, north.y - 10)); + g.draw(new Line2D.Double(north.x, north.y - 15, east.x, east.y - 15)); + g.draw(new Line2D.Double(east.x, east.y - 20, east.x, east.y - 10)); + g.draw(new Line2D.Double(north.x, north.y - 15, west.x, west.y - 15)); + g.draw(new Line2D.Double(west.x, west.y - 20, west.x, west.y - 10)); + + g.setColor(Color.black); + g.setStroke(new BasicStroke(1)); + // Same points, but in thin black. + g.draw(new Line2D.Double(north.x, north.y - 20, north.x, north.y - 10)); + g.draw(new Line2D.Double(north.x, north.y - 15, east.x, east.y - 15)); + g.draw(new Line2D.Double(east.x, east.y - 20, east.x, east.y - 10)); + g.draw(new Line2D.Double(north.x, north.y - 15, west.x, west.y - 15)); + g.draw(new Line2D.Double(west.x, west.y - 20, west.x, west.y - 10)); + + String displayString = + NumberFormat.getInstance().format(isometricDistance(renderer, north, east)); + GraphicsUtil.drawBoxedString(g, displayString, (int) (north.x + 25), (int) (north.y - 25)); + displayString = NumberFormat.getInstance().format(isometricDistance(renderer, north, west)); + GraphicsUtil.drawBoxedString(g, displayString, (int) (north.x - 25), (int) (north.y - 25)); + } + } + public static void drawDiamondMeasurement(ZoneRenderer renderer, Graphics2D g, Shape diamond) { double[] north = null; double[] west = null; From d24e52ae1fbec27bb5d8f80f54d97a94010171e1 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 14:25:50 -0700 Subject: [PATCH 140/146] Implement new strategies for drawing-like tools These will form the basis of a new set of tools for drawing, FoW, and topology. Instead of a spawling class hierarchy with complex interdependencies between each type of tool, the new approach will favour these new strategies for defining the shape, while the tool will define what to do with the shape, e.g., add a drawing, expose some fog, or add some topology. By using these new strategies, there will be no more need for FoW and topology tools to inherit from any drawing tools or vice versa. --- .../client/tool/drawing/CrossStrategy.java | 42 +++++++ .../client/tool/drawing/DrawingResult.java | 20 +++ .../tool/drawing/IsoRectangleStrategy.java | 76 ++++++++++++ .../client/tool/drawing/Measurement.java | 26 ++++ .../client/tool/drawing/OvalStrategy.java | 58 +++++++++ .../client/tool/drawing/PolyLineStrategy.java | 114 ++++++++++++++++++ .../tool/drawing/RectangleStrategy.java | 36 ++++++ .../maptool/client/tool/drawing/Strategy.java | 90 ++++++++++++++ .../rptools/maptool/util/GraphicsUtil.java | 13 +- 9 files changed, 471 insertions(+), 4 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/CrossStrategy.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/DrawingResult.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/IsoRectangleStrategy.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/Measurement.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/OvalStrategy.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineStrategy.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/RectangleStrategy.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/Strategy.java diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/CrossStrategy.java b/src/main/java/net/rptools/maptool/client/tool/drawing/CrossStrategy.java new file mode 100644 index 0000000000..ab1126330b --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/CrossStrategy.java @@ -0,0 +1,42 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.geom.Path2D; +import javax.annotation.Nullable; +import net.rptools.maptool.model.ZonePoint; + +public class CrossStrategy implements Strategy { + @Override + public ZonePoint startNewAtPoint(ZonePoint point) { + return point; + } + + @Override + public @Nullable DrawingResult getShape( + ZonePoint state, ZonePoint currentPoint, boolean centerOnOrigin, boolean isFilled) { + var bounds = Strategy.normalizedRectangle(state, currentPoint, false); + if (bounds.width == 0 && bounds.height == 0) { + return null; + } + + var path = new Path2D.Double(); + path.moveTo(bounds.x, bounds.y); + path.lineTo(bounds.x + bounds.width, bounds.y + bounds.height); + path.moveTo(bounds.x, bounds.y + bounds.height); + path.lineTo(bounds.x + bounds.width, bounds.y); + return new DrawingResult(path, null); + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingResult.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingResult.java new file mode 100644 index 0000000000..1e12044606 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingResult.java @@ -0,0 +1,20 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Shape; +import javax.annotation.Nullable; + +public record DrawingResult(Shape shape, @Nullable Measurement measurement) {} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/IsoRectangleStrategy.java b/src/main/java/net/rptools/maptool/client/tool/drawing/IsoRectangleStrategy.java new file mode 100644 index 0000000000..fbe06c7194 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/IsoRectangleStrategy.java @@ -0,0 +1,76 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Point; +import java.awt.Polygon; +import java.util.Arrays; +import java.util.Collections; +import javax.annotation.Nullable; +import net.rptools.maptool.model.ZonePoint; + +public class IsoRectangleStrategy implements Strategy { + @Override + public ZonePoint startNewAtPoint(ZonePoint point) { + return point; + } + + private Point toPoint(ZonePoint point) { + return new Point(point.x, point.y); + } + + @Override + public @Nullable DrawingResult getShape( + ZonePoint origin_, ZonePoint currentPoint_, boolean centerOnOrigin, boolean isFilled) { + // Inversion check is not strictly needed, but simplifies some case work below. + var invertedY = currentPoint_.y < origin_.y; + var origin = toPoint(invertedY ? currentPoint_ : origin_); + var currentPoint = toPoint(invertedY ? origin_ : currentPoint_); + + final double diffX = (currentPoint.x - origin.x) / 2.; + final double diffY = currentPoint.y - origin.y; + assert diffY >= 0 : "diffY should be forced positive by the above inversion check"; + + var p1 = new Point((int) (origin.x + diffX + diffY), (int) (origin.y + (diffY + diffX) / 2)); + var p2 = new Point((int) (origin.x + diffX - diffY), (int) (origin.y + (diffY - diffX) / 2)); + + var points = new Point[] {origin, p1, currentPoint, p2}; + // For the sake of measurements, we need to know which point is in each compass direction. + // Check for edge cases, and force order of `points` to be north, east, south, west. + if (diffY < Math.abs(diffX)) { + // Inverted over the y = x / 2 axis. First rotate either p1 or p2 into north position... + Collections.rotate(Arrays.asList(points), diffX > 0 ? 1 : -1); + // ... then swap roles of origin and currentPoint due to the inversion. + Collections.swap(Arrays.asList(points), 1, 3); + } + + var xPoints = + new int[] { + points[0].x, points[1].x, points[2].x, points[3].x, + }; + var yPoints = + new int[] { + points[0].y, points[1].y, points[2].y, points[3].y, + }; + var polygon = new Polygon(xPoints, yPoints, 4); + var bounds = polygon.getBounds(); + if (bounds.width == 0 && bounds.height == 0) { + return null; + } + + return new DrawingResult( + polygon, new Measurement.IsoRectangular(points[0], points[3], points[1])); + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/Measurement.java b/src/main/java/net/rptools/maptool/client/tool/drawing/Measurement.java new file mode 100644 index 0000000000..e5b2f573b2 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/Measurement.java @@ -0,0 +1,26 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +public sealed interface Measurement { + record Rectangular(Rectangle2D bounds) implements Measurement {} + + record LineSegment(Point2D p1, Point2D p2) implements Measurement {} + + record IsoRectangular(Point2D north, Point2D west, Point2D east) implements Measurement {} +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalStrategy.java b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalStrategy.java new file mode 100644 index 0000000000..196121a26d --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalStrategy.java @@ -0,0 +1,58 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Shape; +import java.awt.geom.Ellipse2D; +import javax.annotation.Nullable; +import net.rptools.maptool.model.ZonePoint; +import net.rptools.maptool.util.GraphicsUtil; + +public class OvalStrategy implements Strategy { + private final int steps; + + public OvalStrategy() { + this(0); + } + + public OvalStrategy(int steps) { + this.steps = steps; + } + + @Override + public ZonePoint startNewAtPoint(ZonePoint point) { + return point; + } + + @Override + public @Nullable DrawingResult getShape( + ZonePoint state, ZonePoint currentPoint, boolean centerOnOrigin, boolean isFilled) { + var bounds = Strategy.normalizedRectangle(state, currentPoint, centerOnOrigin); + if (bounds.width == 0 && bounds.height == 0) { + return null; + } + + Shape shape; + if (steps <= 0) { + shape = new Ellipse2D.Double(bounds.x, bounds.y, bounds.width, bounds.height); + } else { + shape = + GraphicsUtil.createLineSegmentEllipsePath( + bounds.x, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height, steps); + } + + return new DrawingResult(shape, new Measurement.Rectangular(bounds)); + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineStrategy.java b/src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineStrategy.java new file mode 100644 index 0000000000..53036a0b08 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineStrategy.java @@ -0,0 +1,114 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Polygon; +import java.awt.Shape; +import java.awt.geom.Path2D; +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import net.rptools.maptool.model.ZonePoint; + +public class PolyLineStrategy implements Strategy> { + private final boolean isFreehand; + + public PolyLineStrategy(boolean isFreehand) { + this.isFreehand = isFreehand; + } + + @Override + public boolean isFreehand() { + return isFreehand; + } + + @Override + public boolean isLinear() { + return true; + } + + @Override + public List startNewAtPoint(ZonePoint point) { + var points = new ArrayList(); + points.addLast(new ZonePoint(point)); + return points; + } + + @Override + public void pushPoint(List state, ZonePoint point) { + state.addLast(new ZonePoint(point)); + } + + @Override + public @Nullable DrawingResult getShape( + List state, ZonePoint currentPoint, boolean centerOnOrigin, boolean isFilled) { + var trimmedPoints = new ArrayList<>(state); + trimmedPoints.addLast(currentPoint); + trim(trimmedPoints); + assert !trimmedPoints.isEmpty() : "The list will always have at least the origin point"; + + Shape result; + if (isFilled && trimmedPoints.size() > 2) { + var xPoints = new int[trimmedPoints.size()]; + var yPoints = new int[trimmedPoints.size()]; + for (int i = 0; i < trimmedPoints.size(); ++i) { + var point = trimmedPoints.get(i); + xPoints[i] = point.x; + yPoints[i] = point.y; + } + result = new Polygon(xPoints, yPoints, trimmedPoints.size()); + } else { + var newPath = new Path2D.Double(); + var first = trimmedPoints.getFirst(); + newPath.moveTo(first.x, first.y); + for (var point : trimmedPoints.subList(1, trimmedPoints.size())) { + newPath.lineTo(point.x, point.y); + } + result = newPath; + } + + Measurement measurement = null; + if (!isFreehand()) { + ZonePoint last = trimmedPoints.removeLast(); + ZonePoint secondLast = trimmedPoints.isEmpty() ? last : trimmedPoints.removeLast(); + + measurement = + new Measurement.LineSegment( + new Point2D.Double(secondLast.x, secondLast.y), new Point2D.Double(last.x, last.y)); + } + + return new DrawingResult(result, measurement); + } + + private void trim(List points) { + if (points.isEmpty()) { + return; + } + + ZonePoint previous = null; + var iterator = points.iterator(); + while (iterator.hasNext()) { + var current = iterator.next(); + + if (previous != null && previous.equals(current)) { + iterator.remove(); + continue; + } + + previous = current; + } + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleStrategy.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleStrategy.java new file mode 100644 index 0000000000..9fc75bcfae --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleStrategy.java @@ -0,0 +1,36 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import javax.annotation.Nullable; +import net.rptools.maptool.model.ZonePoint; + +public class RectangleStrategy implements Strategy { + @Override + public ZonePoint startNewAtPoint(ZonePoint point) { + return point; + } + + @Override + public @Nullable DrawingResult getShape( + ZonePoint state, ZonePoint currentPoint, boolean centerOnOrigin, boolean isFilled) { + var bounds = Strategy.normalizedRectangle(state, currentPoint, centerOnOrigin); + if (bounds.width == 0 && bounds.height == 0) { + return null; + } + + return new DrawingResult(bounds, new Measurement.Rectangular(bounds)); + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/Strategy.java b/src/main/java/net/rptools/maptool/client/tool/drawing/Strategy.java new file mode 100644 index 0000000000..9406acc0d1 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/Strategy.java @@ -0,0 +1,90 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Rectangle; +import javax.annotation.Nullable; +import net.rptools.maptool.model.ZonePoint; + +public interface Strategy { + /** + * Check if the tool is a freehand tool. + * + *

Freehand tools have a different flow to other tools. Other tools are click-based, freehand + * tools are dragged-based. + * + * @return {@code true} if the strategy is for a freehand tool. + */ + default boolean isFreehand() { + return false; + } + + /** + * Check if the tool is a linear tool. + * + *

Linear tools support snap-to-center, but not origin-as-center since they don't have a real + * center. + * + * @return {@code true} if the strategy is for a linear tool. + */ + default boolean isLinear() { + return false; + } + + /** + * Start a new drawing using {@code point} as the first point. + * + * @param point The origin point for the new drawing. + */ + StateT startNewAtPoint(ZonePoint point); + + /** + * For linear tools, add {@code point} to the {@code state}. + * + *

Non-linear tools ignore this. + */ + default void pushPoint(StateT state, ZonePoint point) {} + + /** + * Get the current shape of the tool. + * + * @param state The current state of the tool. + * @param currentPoint The current position of the cursor. + * @param centerOnOrigin For tools that support it, {@code true} indicates that the shape should + * be centered on the original point. + * @param isFilled For linear tools, {@code true} indicates that the shape should be closed, while + * {@code false} indicates it should remain open. + * @return The shape of the new topology, or {@code null} to indicate there is nothing to add or + * remove. + */ + @Nullable + DrawingResult getShape( + StateT state, ZonePoint currentPoint, boolean centerOnOrigin, boolean isFilled); + + static Rectangle normalizedRectangle(ZonePoint p1, ZonePoint p2, boolean p1IsCenter) { + if (p1IsCenter) { + var halfWidth = Math.abs(p2.x - p1.x); + var halfHeight = Math.abs(p2.y - p1.y); + return new Rectangle(p1.x - halfWidth, p1.y - halfHeight, 2 * halfWidth, 2 * halfHeight); + } + + // AWT doesn't like drawing rectangles with negative width or height. So normalize it first. + int minX = Math.min(p1.x, p2.x); + int maxX = Math.max(p1.x, p2.x); + int minY = Math.min(p1.y, p2.y); + int maxY = Math.max(p1.y, p2.y); + return new Rectangle(minX, minY, maxX - minX, maxY - minY); + } +} diff --git a/src/main/java/net/rptools/maptool/util/GraphicsUtil.java b/src/main/java/net/rptools/maptool/util/GraphicsUtil.java index d59fbeb891..a2fa0ee24c 100644 --- a/src/main/java/net/rptools/maptool/util/GraphicsUtil.java +++ b/src/main/java/net/rptools/maptool/util/GraphicsUtil.java @@ -29,6 +29,7 @@ import java.awt.Shape; import java.awt.geom.Area; import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collections; @@ -304,6 +305,11 @@ public static Area createLineSegmentEllipse(int x1, int y1, int x2, int y2, int public static Area createLineSegmentEllipse( double x1, double y1, double x2, double y2, int steps) { + return new Area(createLineSegmentEllipsePath(x1, y1, x2, y2, steps)); + } + + public static Path2D createLineSegmentEllipsePath( + double x1, double y1, double x2, double y2, int steps) { double x = Math.min(x1, x2); double y = Math.min(y1, y2); @@ -322,21 +328,20 @@ public static Area createLineSegmentEllipse( double a = w / 2; double b = h / 2; - boolean firstMove = true; for (double t = -Math.PI; t <= Math.PI; t += (2 * Math.PI / steps)) { + // TODO Why do we accept double inputs, but round/cast to int here? int px = (int) Math.round(x + a * Math.cos(t)); int py = (int) Math.round(y + b * Math.sin(t)); - if (firstMove) { + if (path.getCurrentPoint() == null) { path.moveTo(px, py); - firstMove = false; } else { path.lineTo(px, py); } } path.closePath(); - return new Area(path); + return path; } public static void renderSoftClipping(Graphics2D g, Shape shape, int width, double initialAlpha) { From fcffcd1e8aeebe503e6f3906f81c5e43f1651918 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 15:00:57 -0700 Subject: [PATCH 141/146] Replace existing topology tool implementations with one class The new `TopologyTool` uses the previously introduced strategies to define all topology tools. They are instance-based, using the recent supporting change to the toolbox. `TopologyTool` merely interprets the results of the strategies. The strategies are responsible for defining the shapes, but have no knowledge of what the shape will be used for. --- .../tool/drawing/CrossTopologyTool.java | 135 ----------- .../tool/drawing/DiamondTopologyTool.java | 121 --------- .../drawing/HollowDiamondTopologyTool.java | 120 --------- .../tool/drawing/HollowOvalTopologyTool.java | 143 ----------- .../drawing/HollowRectangleTopologyTool.java | 132 ---------- .../client/tool/drawing/OvalTopologyTool.java | 129 ---------- .../tool/drawing/PolyLineTopologyTool.java | 70 ------ .../tool/drawing/PolygonTopologyTool.java | 132 ---------- .../tool/drawing/RectangleTopologyTool.java | 123 ---------- .../client/tool/drawing/TopologyTool.java | 229 ++++++++++++++++++ .../maptool/client/ui/ToolbarPanel.java | 66 ++++- 11 files changed, 286 insertions(+), 1114 deletions(-) delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/CrossTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/HollowDiamondTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/HollowOvalTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/HollowRectangleTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/OvalTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTopologyTool.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/TopologyTool.java diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/CrossTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/CrossTopologyTool.java deleted file mode 100644 index e7a6ac75a9..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/CrossTopologyTool.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import java.awt.geom.Point2D; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Cross; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.util.GraphicsUtil; - -/** - * @author CoveredInFish - */ -public class CrossTopologyTool extends AbstractDrawingTool implements MouseMotionListener { - private static final long serialVersionUID = 3258413928311830323L; - - protected Cross cross; - - public CrossTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.crosstopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.crosstopology.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, cross, Pen.MODE_TRANSPARENT); - } - - @Override - public void mousePressed(MouseEvent e) { - ZonePoint zp = getPoint(e); - - if (SwingUtilities.isLeftMouseButton(e)) { - if (cross == null) { - cross = new Cross(zp.x, zp.y, zp.x, zp.y); - } else { - cross.getEndPoint().x = zp.x; - cross.getEndPoint().y = zp.y; - - int x1 = Math.min(cross.getStartPoint().x, cross.getEndPoint().x); - int x2 = Math.max(cross.getStartPoint().x, cross.getEndPoint().x); - int y1 = Math.min(cross.getStartPoint().y, cross.getEndPoint().y); - int y2 = Math.max(cross.getStartPoint().y, cross.getEndPoint().y); - - // Area area = new Area(new Rectangle(x1-1, y1-1, x2 - x1 + 2, y2 - y1 + 2)); - - // Area area = new Area( new Line2D.Double(x1,y1,x2,y2)); - // area.add( new Area(new Line2D.Double(x1,y2,x2,y1))); - - Area area = - GraphicsUtil.createLine(1, new Point2D.Double(x1, y1), new Point2D.Double(x2, y2)); - area.add( - GraphicsUtil.createLine(1, new Point2D.Double(x1, y2), new Point2D.Double(x2, y1))); - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - - cross = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (cross == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - - ZonePoint p = getPoint(e); - if (cross != null) { - cross.getEndPoint().x = p.x; - cross.getEndPoint().y = p.y; - renderer.repaint(); - } - } - - /** Stop drawing a cross and repaint the zone. */ - @Override - public void resetTool() { - if (cross != null) { - cross = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTopologyTool.java deleted file mode 100644 index 20a46db53b..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTopologyTool.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.Shape; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -public class DiamondTopologyTool extends AbstractDrawingTool implements MouseMotionListener { - - private static final long serialVersionUID = -1497583181619555786L; - protected Shape diamond; - protected ZonePoint originPoint; - - public DiamondTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.isorectangletopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.isorectangletopology.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, diamond); - } - - @Override - public void mousePressed(MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e)) { - ZonePoint zp = getPoint(e); - - if (diamond == null) { - originPoint = zp; - diamond = createDiamond(originPoint, originPoint); - } else { - diamond = createDiamond(originPoint, zp); - - if (diamond.getBounds().width == 0 || diamond.getBounds().height == 0) { - diamond = null; - renderer.repaint(); - return; - } - Area area = new ShapeDrawable(diamond, false).getArea(getZone()); - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - diamond = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (diamond == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - - ZonePoint zp = getPoint(e); - if (diamond != null) { - diamond = createDiamond(originPoint, zp); - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (diamond != null) { - diamond = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowDiamondTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/HollowDiamondTopologyTool.java deleted file mode 100644 index cdf7d9bc8e..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowDiamondTopologyTool.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.Shape; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Pen; - -public class HollowDiamondTopologyTool extends AbstractDrawingTool implements MouseMotionListener { - private static final long serialVersionUID = 7227397975734203085L; - protected Shape diamond; - protected ZonePoint originPoint; - - public HollowDiamondTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.isorectangletopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.isorectangletopologyhollow.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, diamond, Pen.MODE_TRANSPARENT); - } - - @Override - public void mousePressed(MouseEvent e) { - ZonePoint zp = getPoint(e); - if (SwingUtilities.isLeftMouseButton(e)) { - if (diamond == null) { - originPoint = zp; - diamond = createHollowDiamond(originPoint, originPoint, getPen()); - } else { - diamond = createHollowDiamond(originPoint, zp, getPen()); - // diamond = createDiamond(originPoint, zp); - - if (diamond.getBounds().width == 0 || diamond.getBounds().height == 0) { - diamond = null; - renderer.repaint(); - return; - } - Area area = new Area(diamond); - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - diamond = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (diamond == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - - ZonePoint zp = getPoint(e); - if (diamond != null) { - diamond = createDiamond(originPoint, zp); - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (diamond != null) { - diamond = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowOvalTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/HollowOvalTopologyTool.java deleted file mode 100644 index 93076a42bf..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowOvalTopologyTool.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Oval; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.util.GraphicsUtil; - -public class HollowOvalTopologyTool extends AbstractDrawingTool implements MouseMotionListener { - - private static final long serialVersionUID = 3258413928311830325L; - - protected Oval oval; - private ZonePoint originPoint; - - public HollowOvalTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.ovaltopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.ovaltopologyhollow.tooltip"; - } - - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, oval, Pen.MODE_TRANSPARENT); - } - - public void mousePressed(MouseEvent e) { - - if (SwingUtilities.isLeftMouseButton(e)) { - ZonePoint zp = getPoint(e); - - if (oval == null) { - oval = new Oval(zp.x, zp.y, zp.x, zp.y); - originPoint = zp; - } else { - oval.getEndPoint().x = zp.x; - oval.getEndPoint().y = zp.y; - - Area area = - GraphicsUtil.createLineSegmentEllipse( - oval.getStartPoint().x, - oval.getStartPoint().y, - oval.getEndPoint().x, - oval.getEndPoint().y, - 10); - - // Still use the whole area if it's an erase action - if (!isEraser(e)) { - int x1 = Math.min(oval.getStartPoint().x, oval.getEndPoint().x) + 2; - int y1 = Math.min(oval.getStartPoint().y, oval.getEndPoint().y) + 2; - - int x2 = Math.max(oval.getStartPoint().x, oval.getEndPoint().x) - 2; - int y2 = Math.max(oval.getStartPoint().y, oval.getEndPoint().y) - 2; - - Area innerArea = GraphicsUtil.createLineSegmentEllipse(x1, y1, x2, y2, 10); - area.subtract(innerArea); - } - - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - oval = null; - } - - setIsEraser(isEraser(e)); - } - - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - - if (oval == null) { - super.mouseDragged(e); - } - } - - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - - if (oval != null) { - ZonePoint sp = getPoint(e); - - oval.getEndPoint().x = sp.x; - oval.getEndPoint().y = sp.y; - oval.getStartPoint().x = originPoint.x - (sp.x - originPoint.x); - oval.getStartPoint().y = originPoint.y - (sp.y - originPoint.y); - - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - public void resetTool() { - if (oval != null) { - oval = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowRectangleTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/HollowRectangleTopologyTool.java deleted file mode 100644 index 4cdba8717e..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowRectangleTopologyTool.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.Rectangle; - -/** - * @author drice - */ -public class HollowRectangleTopologyTool extends AbstractDrawingTool - implements MouseMotionListener { - private static final long serialVersionUID = 3258413928311830323L; - - protected Rectangle rectangle; - - public HollowRectangleTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.recttopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.recttopologyhollow.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, rectangle, Pen.MODE_TRANSPARENT); - } - - @Override - public void mousePressed(MouseEvent e) { - ZonePoint zp = getPoint(e); - - if (SwingUtilities.isLeftMouseButton(e)) { - if (rectangle == null) { - rectangle = new Rectangle(zp.x, zp.y, zp.x, zp.y); - } else { - rectangle.getEndPoint().x = zp.x; - rectangle.getEndPoint().y = zp.y; - - int x1 = Math.min(rectangle.getStartPoint().x, rectangle.getEndPoint().x); - int x2 = Math.max(rectangle.getStartPoint().x, rectangle.getEndPoint().x); - int y1 = Math.min(rectangle.getStartPoint().y, rectangle.getEndPoint().y); - int y2 = Math.max(rectangle.getStartPoint().y, rectangle.getEndPoint().y); - - Area area = new Area(new java.awt.Rectangle(x1 - 1, y1 - 1, x2 - x1 + 2, y2 - y1 + 2)); - - // Still use the whole area if it's an erase action - if (!isEraser(e)) { - Area innerArea = - new Area(new java.awt.Rectangle(x1 + 1, y1 + 1, x2 - x1 - 2, y2 - y1 - 2)); - area.subtract(innerArea); - } - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - rectangle = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (rectangle == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - - ZonePoint p = getPoint(e); - if (rectangle != null) { - rectangle.getEndPoint().x = p.x; - rectangle.getEndPoint().y = p.y; - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (rectangle != null) { - rectangle = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTopologyTool.java deleted file mode 100644 index 50293f702f..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTopologyTool.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Oval; -import net.rptools.maptool.util.GraphicsUtil; - -public class OvalTopologyTool extends AbstractDrawingTool implements MouseMotionListener { - - private static final long serialVersionUID = 3258413928311830321L; - - protected Oval oval; - private ZonePoint originPoint; - - public OvalTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.ovaltopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.ovaltopology.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, oval); - } - - @Override - public void mousePressed(MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e)) { - ZonePoint zp = getPoint(e); - - if (oval == null) { - oval = new Oval(zp.x, zp.y, zp.x, zp.y); - originPoint = zp; - } else { - oval.getEndPoint().x = zp.x; - oval.getEndPoint().y = zp.y; - - Area area = - GraphicsUtil.createLineSegmentEllipse( - oval.getStartPoint().x, - oval.getStartPoint().y, - oval.getEndPoint().x, - oval.getEndPoint().y, - 10); - - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - oval = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (oval == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - if (oval != null) { - ZonePoint sp = getPoint(e); - - oval.getEndPoint().x = sp.x; - oval.getEndPoint().y = sp.y; - oval.getStartPoint().x = originPoint.x - (sp.x - originPoint.x); - oval.getStartPoint().y = originPoint.y - (sp.y - originPoint.y); - - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (oval != null) { - oval = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineTopologyTool.java deleted file mode 100644 index 16dda8be06..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineTopologyTool.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Point; -import java.awt.Polygon; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import net.rptools.maptool.client.AppStyle; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.LineSegment; -import net.rptools.maptool.model.drawing.Pen; - -/** Tool for drawing freehand lines. */ -public class PolyLineTopologyTool extends PolygonTopologyTool implements MouseMotionListener { - private static final long serialVersionUID = 3258132466219627316L; - - public PolyLineTopologyTool() {} - - @Override - public String getTooltip() { - return "tool.polylinetopo.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.poly.instructions"; - } - - protected boolean isBackgroundFill(MouseEvent e) { - return false; - } - - protected Pen getPen() { - - Pen pen = new Pen(MapTool.getFrame().getPen()); - pen.setEraser(isEraser()); - pen.setForegroundMode(Pen.MODE_SOLID); - pen.setBackgroundMode(Pen.MODE_TRANSPARENT); - pen.setThickness(2.0f); - pen.setOpacity(AppStyle.topologyRemoveColor.getAlpha() / 255.0f); - pen.setPaint( - new DrawableColorPaint( - isEraser() ? AppStyle.topologyRemoveColor : AppStyle.topologyAddColor)); - - return pen; - } - - protected Polygon getPolygon(LineSegment line) { - Polygon polygon = new Polygon(); - for (Point point : line.getPoints()) { - polygon.addPoint(point.x, point.y); - } - - return polygon; - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTopologyTool.java deleted file mode 100644 index c4eb1e4f45..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTopologyTool.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.BasicStroke; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Polygon; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import java.awt.geom.Path2D; -import net.rptools.maptool.client.AppStyle; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.LineSegment; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** Tool for drawing freehand lines. */ -public class PolygonTopologyTool extends LineTool implements MouseMotionListener { - - private static final long serialVersionUID = 3258132466219627316L; - - public PolygonTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - protected boolean drawMeasurement() { - return false; - } - - @Override - public String getTooltip() { - return "tool.polytopo.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.poly.instructions"; - } - - @Override - protected boolean isBackgroundFill(MouseEvent e) { - return true; - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - Area area = new Area(); - - if (drawable instanceof LineSegment) { - LineSegment line = (LineSegment) drawable; - BasicStroke stroke = - new BasicStroke(pen.getThickness(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); - - Path2D path = new Path2D.Double(); - Point lastPoint = null; - - for (Point point : line.getPoints()) { - if (path.getCurrentPoint() == null) { - path.moveTo(point.x, point.y); - } else if (!point.equals(lastPoint)) { - path.lineTo(point.x, point.y); - lastPoint = point; - } - } - - area.add(new Area(stroke.createStrokedShape(path))); - } else { - area = new Area(((ShapeDrawable) drawable).getShape()); - } - MapTool.serverCommand() - .updateTopology(getZone(), area, pen.isEraser(), getZone().getTopologyTypes()); - } - - @Override - protected Pen getPen() { - Pen pen = new Pen(MapTool.getFrame().getPen()); - pen.setEraser(isEraser()); - pen.setForegroundMode(Pen.MODE_TRANSPARENT); - pen.setBackgroundMode(Pen.MODE_SOLID); - pen.setThickness(1.0f); - pen.setOpacity(AppStyle.topologyRemoveColor.getAlpha() / 255.0f); - pen.setPaint( - new DrawableColorPaint( - isEraser() ? AppStyle.topologyRemoveColor : AppStyle.topologyAddColor)); - return pen; - } - - @Override - protected Polygon getPolygon(LineSegment line) { - Polygon polygon = new Polygon(); - for (Point point : line.getPoints()) { - polygon.addPoint(point.x, point.y); - } - return polygon; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g); - super.paintOverlay(renderer, g); - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTopologyTool.java deleted file mode 100644 index cb9a045595..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTopologyTool.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Rectangle; - -/** - * @author drice - */ -public class RectangleTopologyTool extends AbstractDrawingTool implements MouseMotionListener { - private static final long serialVersionUID = 3258413928311830323L; - - protected Rectangle rectangle; - - public RectangleTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.recttopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.recttopology.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, rectangle); - } - - @Override - public void mousePressed(MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e)) { - ZonePoint sp = getPoint(e); - - if (rectangle == null) { - rectangle = new Rectangle(sp.x, sp.y, sp.x, sp.y); - } else { - rectangle.getEndPoint().x = sp.x; - rectangle.getEndPoint().y = sp.y; - - int x1 = Math.min(rectangle.getStartPoint().x, rectangle.getEndPoint().x); - int x2 = Math.max(rectangle.getStartPoint().x, rectangle.getEndPoint().x); - int y1 = Math.min(rectangle.getStartPoint().y, rectangle.getEndPoint().y); - int y2 = Math.max(rectangle.getStartPoint().y, rectangle.getEndPoint().y); - - Area area = new Area(new java.awt.Rectangle(x1, y1, x2 - x1, y2 - y1)); - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - rectangle = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (rectangle == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - - ZonePoint p = getPoint(e); - if (rectangle != null) { - rectangle.getEndPoint().x = p.x; - rectangle.getEndPoint().y = p.y; - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (rectangle != null) { - rectangle = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/TopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/TopologyTool.java new file mode 100644 index 0000000000..669f2d7a04 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/TopologyTool.java @@ -0,0 +1,229 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.event.MouseEvent; +import java.awt.geom.Area; +import java.util.List; +import javax.annotation.Nullable; +import javax.swing.SwingUtilities; +import net.rptools.maptool.client.AppStyle; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; +import net.rptools.maptool.model.Token; +import net.rptools.maptool.model.Zone; +import net.rptools.maptool.model.ZonePoint; + +public final class TopologyTool extends AbstractDrawingLikeTool { + private final String instructionKey; + private final String tooltipKey; + private final boolean isFilled; + private final Strategy strategy; + + /** The current state of the tool. If {@code null}, nothing is being drawn right now. */ + private @Nullable StateT state; + + private ZonePoint currentPoint = new ZonePoint(0, 0); + // Topology never supports center on origin right now, but it should in the future. + private boolean centerOnOrigin; + + public TopologyTool( + String instructionKey, String tooltipKey, boolean isFilled, Strategy strategy) { + this.instructionKey = instructionKey; + this.tooltipKey = tooltipKey; + this.isFilled = isFilled; + this.strategy = strategy; + // Consistency with topology tools before refactoring. Can be updated as part of #5002. + this.centerOnOrigin = this.strategy instanceof OvalStrategy; + } + + @Override + public String getInstructions() { + return instructionKey; + } + + @Override + public String getTooltip() { + return tooltipKey; + } + + @Override + public boolean isAvailable() { + return MapTool.getPlayer().isGM(); + } + + @Override + protected boolean isLinearTool() { + return strategy.isLinear(); + } + + /** If currently drawing, stop and clear it. */ + @Override + protected void resetTool() { + if (state != null) { + state = null; + renderer.repaint(); + } else { + super.resetTool(); + } + } + + private BasicStroke getLineStroke() { + return new BasicStroke(2.f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); + } + + private void submit(Shape shape) { + Area area; + if (shape instanceof Area tmpArea) { + area = tmpArea; + } else if (isFilled) { + // Fill the shape without stroking. + area = new Area(shape); + } else { + // Stroke the shape into an area. + var stroke = getLineStroke(); + area = new Area(stroke.createStrokedShape(shape)); + } + + MapTool.serverCommand() + .updateTopology(getZone(), area, isEraser(), getZone().getTopologyTypes()); + } + + private Area getTokenTopology(Zone.TopologyType topologyType) { + List topologyTokens = getZone().getTokensWithTopology(topologyType); + + Area tokenTopology = new Area(); + for (Token topologyToken : topologyTokens) { + tokenTopology.add(topologyToken.getTransformedTopology(topologyType)); + } + + return tokenTopology; + } + + @Override + public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { + if (!MapTool.getPlayer().isGM()) { + // Redundant check since the tool should not be available otherwise. + return; + } + + Zone zone = renderer.getZone(); + + Graphics2D g2 = (Graphics2D) g.create(); + g2.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); + g2.scale(renderer.getScale(), renderer.getScale()); + + g2.setColor(AppStyle.tokenMblColor); + g2.fill(getTokenTopology(Zone.TopologyType.MBL)); + g2.setColor(AppStyle.tokenTopologyColor); + g2.fill(getTokenTopology(Zone.TopologyType.WALL_VBL)); + g2.setColor(AppStyle.tokenHillVblColor); + g2.fill(getTokenTopology(Zone.TopologyType.HILL_VBL)); + g2.setColor(AppStyle.tokenPitVblColor); + g2.fill(getTokenTopology(Zone.TopologyType.PIT_VBL)); + g2.setColor(AppStyle.tokenCoverVblColor); + g2.fill(getTokenTopology(Zone.TopologyType.COVER_VBL)); + + g2.setColor(AppStyle.topologyTerrainColor); + g2.fill(zone.getTopology(Zone.TopologyType.MBL)); + + g2.setColor(AppStyle.topologyColor); + g2.fill(zone.getTopology(Zone.TopologyType.WALL_VBL)); + + g2.setColor(AppStyle.hillVblColor); + g2.fill(zone.getTopology(Zone.TopologyType.HILL_VBL)); + + g2.setColor(AppStyle.pitVblColor); + g2.fill(zone.getTopology(Zone.TopologyType.PIT_VBL)); + + g2.setColor(AppStyle.coverVblColor); + g2.fill(zone.getTopology(Zone.TopologyType.COVER_VBL)); + + if (state != null) { + var result = strategy.getShape(state, currentPoint, centerOnOrigin, false); + if (result != null) { + var stroke = getLineStroke(); + var color = isEraser() ? AppStyle.topologyRemoveColor : AppStyle.topologyAddColor; + + if (!isFilled || isLinearTool()) { + // Render as a thick line. + g2.setColor(color); + g2.setStroke(stroke); + g2.draw(result.shape()); + } else { + // Render as an area with a thin border. + g2.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 255)); + g2.setStroke( + new BasicStroke( + 1 / (float) renderer.getScale(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)); + g2.draw(result.shape()); + + g2.setColor(color); + g2.fill(result.shape()); + } + } + } + + g2.dispose(); + } + + @Override + public void mouseDragged(MouseEvent e) { + if (state == null) { + // We're not doing anything, so delegate to default behaviour. + super.mouseDragged(e); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + super.mouseMoved(e); + setIsEraser(isEraser(e)); + if (state != null) { + currentPoint = getPoint(e); + renderer.repaint(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + setIsEraser(isEraser(e)); + + if (SwingUtilities.isLeftMouseButton(e)) { + currentPoint = getPoint(e); + + if (state == null) { + state = strategy.startNewAtPoint(currentPoint); + } else { + var result = strategy.getShape(state, currentPoint, centerOnOrigin, isFilled); + state = null; + if (result != null) { + submit(result.shape()); + } + } + renderer.repaint(); + } else if (state != null) { + currentPoint = getPoint(e); + strategy.pushPoint(state, currentPoint); + renderer.repaint(); + } + + super.mousePressed(e); + } +} diff --git a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java index 418ddb28ec..10595a1604 100644 --- a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java @@ -342,32 +342,80 @@ protected void activate() { private OptionPanel createTopologyPanel() { OptionPanel panel = new OptionPanel(); + panel - .add(RectangleTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.recttopology.instructions", + "tool.recttopology.tooltip", + true, + new RectangleStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_BOX)); panel - .add(HollowRectangleTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.recttopology.instructions", + "tool.recttopologyhollow.tooltip", + false, + new RectangleStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_BOX_HOLLOW)); panel - .add(OvalTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.ovaltopology.instructions", + "tool.ovaltopology.tooltip", + true, + // 10 steps to keep number of topology vertices reasonable. + new OvalStrategy(10))) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_OVAL)); panel - .add(HollowOvalTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.ovaltopology.instructions", + "tool.ovaltopologyhollow.tooltip", + false, + // 10 steps to keep number of topology vertices reasonable. + new OvalStrategy(10))) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_OVAL_HOLLOW)); panel - .add(PolygonTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.poly.instructions", + "tool.polytopo.tooltip", + true, + new PolyLineStrategy(false))) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_POLYGON)); panel - .add(PolyLineTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.poly.instructions", + "tool.polylinetopo.tooltip", + false, + new PolyLineStrategy(false))) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_POLYLINE)); panel - .add(CrossTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.crosstopology.instructions", + "tool.crosstopology.tooltip", + false, + new CrossStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_CROSS)); panel - .add(DiamondTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.isorectangletopology.instructions", + "tool.isorectangletopology.tooltip", + true, + new IsoRectangleStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_DIAMOND)); panel - .add(HollowDiamondTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.isorectangletopology.instructions", + "tool.isorectangletopologyhollow.tooltip", + false, + new IsoRectangleStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_DIAMOND_HOLLOW)); // Add with separator to separate mode button group from shape button group. From 282d0ae021303a64b09ecfab598afa192eb90e6d Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 15:07:04 -0700 Subject: [PATCH 142/146] Replace existing expose tool implementations with one class Analogous to `TopologyTool`, we now have `ExposeTool` that can work with any strategy and merely interprets the results. This also fixes a minor bug where the iso rectangle tool did not result in the fog border being used. --- .../tool/drawing/DiamondExposeTool.java | 88 ------- .../client/tool/drawing/ExposeTool.java | 238 ++++++++++++++++++ .../tool/drawing/FreehandExposeTool.java | 108 -------- .../client/tool/drawing/OvalExposeTool.java | 92 ------- .../tool/drawing/PolygonExposeTool.java | 104 -------- .../tool/drawing/RectangleExposeTool.java | 91 ------- .../maptool/client/ui/ToolbarPanel.java | 26 +- .../client/ui/zone/renderer/ZoneRenderer.java | 12 +- 8 files changed, 261 insertions(+), 498 deletions(-) delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/DiamondExposeTool.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/FreehandExposeTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/OvalExposeTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/PolygonExposeTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/RectangleExposeTool.java diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondExposeTool.java deleted file mode 100644 index ee70cf3314..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondExposeTool.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.event.MouseEvent; -import java.awt.geom.Area; -import java.util.Set; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.Pen; - -public class DiamondExposeTool extends DiamondTool { - private static final long serialVersionUID = 2577704127916955192L; - - public DiamondExposeTool() {} - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.rectexpose.instructions"; - } - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - protected boolean isBackgroundFill(MouseEvent e) { - // Expose tools are implied to be filled - return false; - } - - @Override - protected Pen getPen() { - Pen pen = super.getPen(); - pen.setBackgroundMode(Pen.MODE_TRANSPARENT); - pen.setThickness(1); - return pen; - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - if (!MapTool.getPlayer().isGM()) { - MapTool.showError("msg.error.fogexpose"); - MapTool.getFrame().refresh(); - return; - } - Zone zone = getZone(); - Area area = new Area(drawable.getArea(zone)); - Set selectedToks = renderer.getSelectedTokenSet(); - if (pen.isEraser()) { - zone.hideArea(area, selectedToks); - MapTool.serverCommand().hideFoW(zone.getId(), area, selectedToks); - } else { - MapTool.serverCommand().exposeFoW(zone.getId(), area, selectedToks); - } - MapTool.getFrame().refresh(); - } - - @Override - public String getTooltip() { - return "tool.isorectangleexpose.tooltip"; - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java new file mode 100644 index 0000000000..c74c30643f --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java @@ -0,0 +1,238 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.event.MouseEvent; +import java.awt.geom.Area; +import java.util.Set; +import javax.annotation.Nullable; +import javax.swing.SwingUtilities; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.ScreenPoint; +import net.rptools.maptool.client.tool.ToolHelper; +import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; +import net.rptools.maptool.model.GUID; +import net.rptools.maptool.model.Zone; +import net.rptools.maptool.model.ZonePoint; + +public final class ExposeTool extends AbstractDrawingLikeTool { + private final String instructionKey; + private final String tooltipKey; + private final Strategy strategy; + + /** The current state of the tool. If {@code null}, nothing is being drawn right now. */ + private @Nullable StateT state; + + private ZonePoint currentPoint = new ZonePoint(0, 0); + private boolean centerOnOrigin = false; + + public ExposeTool(String instructionKey, String tooltipKey, Strategy strategy) { + this.instructionKey = instructionKey; + this.tooltipKey = tooltipKey; + this.strategy = strategy; + } + + @Override + public String getInstructions() { + return instructionKey; + } + + @Override + public String getTooltip() { + return tooltipKey; + } + + @Override + public boolean isAvailable() { + return MapTool.getPlayer().isGM(); + } + + @Override + protected boolean isLinearTool() { + return strategy.isLinear(); + } + + /** If currently drawing, stop and clear it. */ + @Override + protected void resetTool() { + if (state != null) { + state = null; + renderer.repaint(); + } else { + super.resetTool(); + } + } + + private void submit(Shape shape) { + if (!MapTool.getPlayer().isGM()) { + MapTool.showError("msg.error.fogexpose"); + MapTool.getFrame().refresh(); + return; + } + + Area area; + if (shape instanceof Area tmpArea) { + area = tmpArea; + } else { + // Fill the shape. + area = new Area(shape); + } + + Zone zone = getZone(); + Set selectedToks = renderer.getSelectedTokenSet(); + + if (isEraser()) { + zone.hideArea(area, selectedToks); + MapTool.serverCommand().hideFoW(zone.getId(), area, selectedToks); + } else { + MapTool.serverCommand().exposeFoW(zone.getId(), area, selectedToks); + } + } + + @Override + public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { + Graphics2D g2 = (Graphics2D) g.create(); + g2.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); + g2.scale(renderer.getScale(), renderer.getScale()); + + if (state != null) { + var result = strategy.getShape(state, currentPoint, centerOnOrigin, false); + if (result != null) { + var color = isEraser() ? Color.white : Color.black; + + if (!isLinearTool()) { + // Render the interior for better user feedback. + g2.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 64)); + g2.fill(result.shape()); + } + + // Render the line. + g2.setColor(color); + g2.setStroke( + new BasicStroke( + 1 / (float) renderer.getScale(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)); + g2.draw(result.shape()); + + // Measurements + var measurement = result.measurement(); + switch (measurement) { + case null -> {} + case Measurement.Rectangular rectangular -> { + var rectangle = rectangular.bounds(); + ToolHelper.drawBoxedMeasurement( + renderer, + g, + ScreenPoint.fromZonePoint(renderer, rectangle.getX(), rectangle.getY()), + ScreenPoint.fromZonePoint(renderer, rectangle.getMaxX(), rectangle.getMaxY())); + } + case Measurement.LineSegment lineSegment -> { + var p1 = + ScreenPoint.fromZonePoint( + renderer, lineSegment.p1().getX(), lineSegment.p1().getY()); + var p2 = + ScreenPoint.fromZonePoint( + renderer, lineSegment.p2().getX(), lineSegment.p2().getY()); + ToolHelper.drawMeasurement(renderer, g, p1, p2); + } + case Measurement.IsoRectangular isoRectangular -> { + var north = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.north().getX(), isoRectangular.north().getY()); + var west = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.west().getX(), isoRectangular.west().getY()); + var east = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.east().getX(), isoRectangular.east().getY()); + ToolHelper.drawIsoRectangleMeasurement(renderer, g, north, west, east); + } + } + } + } + + g2.dispose(); + } + + @Override + public void mouseDragged(MouseEvent e) { + if (state == null) { + // We're not doing anything, so delegate to default behaviour. + super.mouseDragged(e); + } else if (strategy.isFreehand()) { + // Extend the line. + setIsEraser(isEraser(e)); + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); // Pointless, but it doesn't hurt for consistency. + strategy.pushPoint(state, currentPoint); + renderer.repaint(); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + super.mouseMoved(e); + setIsEraser(isEraser(e)); + if (state != null) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + renderer.repaint(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + setIsEraser(isEraser(e)); + + if (SwingUtilities.isLeftMouseButton(e)) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + + if (state == null) { + state = strategy.startNewAtPoint(currentPoint); + } else if (!strategy.isFreehand()) { + var result = strategy.getShape(state, currentPoint, centerOnOrigin, true); + state = null; + if (result != null) { + submit(result.shape()); + } + } + renderer.repaint(); + } else if (state != null && !strategy.isFreehand()) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + strategy.pushPoint(state, currentPoint); + renderer.repaint(); + } + + super.mousePressed(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (strategy.isFreehand() && SwingUtilities.isLeftMouseButton(e)) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + var result = strategy.getShape(state, currentPoint, centerOnOrigin, true); + state = null; + if (result != null) { + submit(result.shape()); + } + } + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/FreehandExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/FreehandExposeTool.java deleted file mode 100644 index 059edb246d..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/FreehandExposeTool.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import java.util.Set; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.LineSegment; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** Tool for drawing freehand lines. */ -public class FreehandExposeTool extends FreehandTool implements MouseMotionListener { - private static final long serialVersionUID = 3258132466219627316L; - - public FreehandExposeTool() {} - - @Override - public String getTooltip() { - return "tool.freehandexpose.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.freehandexpose.instructions"; - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - protected Pen getPen() { - Pen pen = super.getPen(); - pen.setThickness(1); - return pen; - } - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - protected boolean isBackgroundFill(MouseEvent e) { - // Expose tools are implied to be filled - return false; - } - - @Override - protected void stopLine(MouseEvent e) { - LineSegment line = getLine(); - - if (line == null) return; // Escape has been pressed - addPoint(e); - completeDrawable(getPen(), line); - resetTool(); - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - if (!MapTool.getPlayer().isGM()) { - MapTool.showError("msg.error.fogexpose"); - MapTool.getFrame().refresh(); - return; - } - Zone zone = getZone(); - - Area area = null; - if (drawable instanceof LineSegment) { - area = new Area(getPolygon((LineSegment) drawable)); - } - if (drawable instanceof ShapeDrawable) { - area = new Area(((ShapeDrawable) drawable).getShape()); - } - Set selectedToks = renderer.getSelectedTokenSet(); - if (pen.isEraser()) { - zone.hideArea(area, selectedToks); - MapTool.serverCommand().hideFoW(zone.getId(), area, selectedToks); - } else { - MapTool.serverCommand().exposeFoW(zone.getId(), area, selectedToks); - } - MapTool.getFrame().refresh(); - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalExposeTool.java deleted file mode 100644 index 6f8d6f72f0..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalExposeTool.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Rectangle; -import java.awt.event.MouseEvent; -import java.awt.geom.Area; -import java.awt.geom.Ellipse2D; -import java.util.Set; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.Pen; - -public class OvalExposeTool extends OvalTool { - private static final long serialVersionUID = -9023090752132286356L; - - public OvalExposeTool() {} - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - protected boolean isBackgroundFill(MouseEvent e) { - // Expose tools are implied to be filled - return false; - } - - @Override - protected Pen getPen() { - Pen pen = super.getPen(); - pen.setBackgroundMode(Pen.MODE_TRANSPARENT); - pen.setThickness(1); - return pen; - } - - @Override - public String getTooltip() { - return "tool.ovalexpose.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.ovalexpose.instructions"; - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - if (!MapTool.getPlayer().isGM()) { - MapTool.showError("msg.error.fogexpose"); - MapTool.getFrame().refresh(); - return; - } - Zone zone = getZone(); - - Rectangle bounds = drawable.getBounds(zone); - Area area = new Area(new Ellipse2D.Double(bounds.x, bounds.y, bounds.width, bounds.height)); - Set selectedToks = renderer.getSelectedTokenSet(); - if (pen.isEraser()) { - zone.hideArea(area, selectedToks); - MapTool.serverCommand().hideFoW(zone.getId(), area, selectedToks); - } else { - MapTool.serverCommand().exposeFoW(zone.getId(), area, selectedToks); - } - MapTool.getFrame().refresh(); - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonExposeTool.java deleted file mode 100644 index 1c7e9d059f..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonExposeTool.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Polygon; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import java.util.Set; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.LineSegment; -import net.rptools.maptool.model.drawing.Pen; - -/** Tool for drawing freehand lines. */ -public class PolygonExposeTool extends PolygonTool implements MouseMotionListener { - private static final long serialVersionUID = 3258132466219627316L; - - public PolygonExposeTool() {} - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getTooltip() { - return "tool.polyexpose.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.polyexpose.instructions"; - } - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - protected boolean isBackgroundFill(MouseEvent e) { - // Expose tools are implied to be filled - return false; - } - - @Override - protected Pen getPen() { - Pen pen = super.getPen(); - pen.setBackgroundMode(Pen.MODE_TRANSPARENT); - pen.setThickness(1); - return pen; - } - - @Override - protected void stopLine(MouseEvent e) { - LineSegment line = getLine(); - - if (line == null) return; // Escape has been pressed - addPoint(e); - completeDrawable(getPen(), line); - resetTool(); - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - if (!MapTool.getPlayer().isGM()) { - MapTool.showError("msg.error.fogexpose"); - MapTool.getFrame().refresh(); - return; - } - Zone zone = getZone(); - - Polygon polygon = getPolygon((LineSegment) drawable); - Area area = new Area(polygon); - Set selectedToks = renderer.getSelectedTokenSet(); - if (pen.isEraser()) { - zone.hideArea(area, selectedToks); - MapTool.serverCommand().hideFoW(zone.getId(), area, selectedToks); - } else { - MapTool.serverCommand().exposeFoW(zone.getId(), area, selectedToks); - } - MapTool.getFrame().refresh(); - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleExposeTool.java deleted file mode 100644 index 7a1336b0bb..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleExposeTool.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Rectangle; -import java.awt.event.MouseEvent; -import java.awt.geom.Area; -import java.util.Set; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.Pen; - -public class RectangleExposeTool extends RectangleTool { - private static final long serialVersionUID = 2072551559910263728L; - - public RectangleExposeTool() {} - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.rectexpose.instructions"; - } - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - protected boolean isBackgroundFill(MouseEvent e) { - // Expose tools are implied to be filled - return false; - } - - @Override - protected Pen getPen() { - Pen pen = super.getPen(); - pen.setBackgroundMode(Pen.MODE_TRANSPARENT); - pen.setThickness(1); - return pen; - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - if (!MapTool.getPlayer().isGM()) { - MapTool.showError("msg.error.fogexpose"); - MapTool.getFrame().refresh(); - return; - } - Zone zone = getZone(); - - Rectangle bounds = drawable.getBounds(zone); - Area area = new Area(bounds); - Set selectedToks = renderer.getSelectedTokenSet(); - if (pen.isEraser()) { - zone.hideArea(area, selectedToks); - MapTool.serverCommand().hideFoW(zone.getId(), area, selectedToks); - } else { - MapTool.serverCommand().exposeFoW(zone.getId(), area, selectedToks); - } - MapTool.getFrame().refresh(); - } - - @Override - public String getTooltip() { - return "tool.rectexpose.tooltip"; - } -} diff --git a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java index 10595a1604..21405ce336 100644 --- a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java @@ -322,21 +322,37 @@ protected void activate() { } } }; + panel - .add(RectangleExposeTool.class) + .addTool( + new ExposeTool<>( + "tool.rectexpose.instructions", "tool.rectexpose.tooltip", new RectangleStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_FOG_EXPOSE_BOX)); panel - .add(OvalExposeTool.class) + .addTool( + new ExposeTool<>( + "tool.ovalexpose.instructions", "tool.ovalexpose.tooltip", new OvalStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_FOG_EXPOSE_OVAL)); panel - .add(PolygonExposeTool.class) + .addTool( + new ExposeTool<>( + "tool.polyexpose.instructions", + "tool.polyexpose.tooltip", + new PolyLineStrategy(false))) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_FOG_EXPOSE_POLYGON)); panel - .add(FreehandExposeTool.class) + .addTool( + new ExposeTool<>( + "tool.freehand.instructions", "tool.freehand.tooltip", new PolyLineStrategy(true))) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_FOG_EXPOSE_FREEHAND)); panel - .add(DiamondExposeTool.class) + .addTool( + new ExposeTool<>( + "tool.rectexpose.instructions", + "tool.isorectangleexpose.tooltip", + new IsoRectangleStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_FOG_EXPOSE_DIAMOND)); + return panel; } diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java index 2e2690e862..b91fb4fe9d 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java @@ -48,10 +48,7 @@ import net.rptools.maptool.client.tool.PointerTool; import net.rptools.maptool.client.tool.StampTool; import net.rptools.maptool.client.tool.Tool; -import net.rptools.maptool.client.tool.drawing.FreehandExposeTool; -import net.rptools.maptool.client.tool.drawing.OvalExposeTool; -import net.rptools.maptool.client.tool.drawing.PolygonExposeTool; -import net.rptools.maptool.client.tool.drawing.RectangleExposeTool; +import net.rptools.maptool.client.tool.drawing.ExposeTool; import net.rptools.maptool.client.ui.Scale; import net.rptools.maptool.client.ui.theme.Borders; import net.rptools.maptool.client.ui.theme.Images; @@ -2723,12 +2720,7 @@ protected void renderTokens( } if (useIF && token.getLayer().supportsVision() && zoneView.isUsingVision()) { Tool tool = MapTool.getFrame().getToolbox().getSelectedTool(); - if (tool - instanceof - RectangleExposeTool // XXX Change to use marker interface such as ExposeTool? - || tool instanceof OvalExposeTool - || tool instanceof FreehandExposeTool - || tool instanceof PolygonExposeTool) { + if (tool instanceof ExposeTool) { selectedBorder = RessourceManager.getBorder(Borders.FOW_TOOLS); } } From c7b7224e5c2c8c2f9099bd4c259dcee1d818005d Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 15:14:45 -0700 Subject: [PATCH 143/146] Replace existing drawing tool implementations with one class Analogous to `TopologyTool` and `ExposeTool`, we now have `DrawingTool` that can work with any strategy and merely interprets the results as drawings to add to the zone. --- .../client/tool/drawing/AbstractLineTool.java | 181 ---------- .../client/tool/drawing/DiamondTool.java | 116 ------ .../client/tool/drawing/DrawingTool.java | 334 ++++++++++++++++++ .../client/tool/drawing/FreehandTool.java | 66 ---- .../maptool/client/tool/drawing/LineTool.java | 77 ---- .../maptool/client/tool/drawing/OvalTool.java | 164 --------- .../client/tool/drawing/PolygonTool.java | 55 --- .../client/tool/drawing/RectangleTool.java | 143 -------- .../maptool/client/ui/ToolbarPanel.java | 31 +- .../ui/drawpanel/DrawPanelPopupMenu.java | 2 +- .../maptool/model/drawing/LineSegment.java | 56 ++- .../maptool/model/drawing/ShapeDrawable.java | 4 - 12 files changed, 409 insertions(+), 820 deletions(-) delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/AbstractLineTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTool.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/FreehandTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/LineTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTool.java diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractLineTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractLineTool.java deleted file mode 100644 index 30b15fe03b..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractLineTool.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Polygon; -import java.awt.event.MouseEvent; -import java.util.Collections; -import java.util.List; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.AppPreferences.RenderQuality; -import net.rptools.maptool.client.ScreenPoint; -import net.rptools.maptool.client.tool.Tool; -import net.rptools.maptool.client.tool.ToolHelper; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.LineSegment; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** Tool for drawing freehand lines. */ -public abstract class AbstractLineTool extends AbstractDrawingTool { - private int currentX; - private int currentY; - - private LineSegment line; - protected boolean drawMeasurementDisabled; - - protected int getCurrentX() { - return currentX; - } - - protected int getCurrentY() { - return currentY; - } - - protected LineSegment getLine() { - return this.line; - } - - protected void startLine(MouseEvent e) { - line = new LineSegment(getPen().getThickness(), getPen().getSquareCap()); - addPoint(e); - } - - protected Point addPoint(MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - return null; - } - ZonePoint zp = getPoint(e); - - if (line == null) return null; // Escape has been pressed - Point ret = new Point(zp.x, zp.y); - - line.getPoints().add(ret); - currentX = zp.x; - currentY = zp.y; - - renderer.repaint(); - return ret; - } - - protected void removePoint(Point p) { - if (line == null) return; // Escape has been pressed - - // Remove most recently added - // TODO: optimize this - Collections.reverse(line.getPoints()); - line.getPoints().remove(p); - Collections.reverse(line.getPoints()); - } - - protected void stopLine(MouseEvent e) { - if (line == null) return; // Escape has been pressed - addPoint(e); - - LineSegment trimLine = getTrimLine(line); - Drawable drawable = trimLine; - if (isBackgroundFill(e) && line.getPoints().size() > 2) { - drawable = new ShapeDrawable(getPolygon(trimLine)); - } - completeDrawable(getPen(), drawable); - - line = null; - currentX = -1; - currentY = -1; - } - - protected Polygon getPolygon(LineSegment line) { - Polygon polygon = new Polygon(); - for (Point point : line.getPoints()) { - polygon.addPoint(point.x, point.y); - } - return polygon; - } - - /** - * Due to mouse movement, a user drawn line often has duplicated points, especially at the end. To - * draw a clean line with miter joints these duplicates should be removed. - * - * @param line the {@link LineSegment} to trim. - * @return the trimmed {@link LineSegment}. - */ - protected LineSegment getTrimLine(LineSegment line) { - LineSegment newLine = new LineSegment(line.getWidth(), line.isSquareCap()); - Point lastPoint = null; - for (Point point : line.getPoints()) { - if (!point.equals(lastPoint)) newLine.getPoints().add(point); - lastPoint = point; - } - return newLine; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - if (line != null) { - // For the line currently being drawn we are more concerned with speed of prettiness - var og = (Graphics2D) g.create(); - RenderQuality.LOW_SCALING.setRenderingHints(og); - Pen pen = getPen(); - pen.setForegroundMode(Pen.MODE_SOLID); - - if (pen.isEraser()) { - pen = new Pen(pen); - pen.setEraser(false); - pen.setPaint(new DrawableColorPaint(Color.white)); - } - paintTransformed(og, renderer, line, pen); - - List pointList = line.getPoints(); - if (!drawMeasurementDisabled && pointList.size() > 1 && drawMeasurement()) { - - Point start = pointList.get(pointList.size() - 2); - Point end = pointList.get(pointList.size() - 1); - - ScreenPoint sp = ScreenPoint.fromZonePoint(renderer, start.x, start.y); - ScreenPoint ep = ScreenPoint.fromZonePoint(renderer, end.x, end.y); - - // ep.y -= 15; - - ToolHelper.drawMeasurement(renderer, og, sp, ep); - } - og.dispose(); - } - } - - protected boolean drawMeasurement() { - return true; - } - - /** - * @see Tool#resetTool() - */ - @Override - protected void resetTool() { - if (line != null) { - line = null; - currentX = -1; - currentY = -1; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTool.java deleted file mode 100644 index 970ae65f30..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTool.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Shape; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.tool.ToolHelper; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -public class DiamondTool extends AbstractDrawingTool implements MouseMotionListener { - - private static final long serialVersionUID = 8239333601131612106L; - protected Shape diamond; - protected ZonePoint originPoint; - - public DiamondTool() {} - - @Override - public String getInstructions() { - return "tool.rect.instructions"; - } - - @Override - public String getTooltip() { - return "tool.isorectangle.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - if (diamond != null) { - Pen pen = getPen(); - if (pen.isEraser()) { - pen = new Pen(pen); - pen.setEraser(false); - pen.setPaint(new DrawableColorPaint(Color.white)); - pen.setBackgroundPaint(new DrawableColorPaint(Color.white)); - } - paintTransformed(g, renderer, new ShapeDrawable(diamond, false), pen); - ToolHelper.drawDiamondMeasurement(renderer, g, diamond); - } - } - - @Override - public void mousePressed(MouseEvent e) { - ZonePoint zp = getPoint(e); - if (SwingUtilities.isLeftMouseButton(e)) { - if (diamond == null) { - originPoint = zp; - diamond = createDiamond(originPoint, originPoint); - } else { - diamond = createDiamond(originPoint, zp); - - if (diamond.getBounds().width == 0 || diamond.getBounds().height == 0) { - diamond = null; - renderer.repaint(); - return; - } - // ToolHelper.drawDiamondMeasurement(renderer, null, diamond); - completeDrawable(getPen(), new ShapeDrawable(diamond, false)); - diamond = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (diamond == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - if (diamond != null) { - ZonePoint p = getPoint(e); - diamond = createDiamond(originPoint, p); - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (diamond != null) { - diamond = null; - originPoint = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java new file mode 100644 index 0000000000..9232325049 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java @@ -0,0 +1,334 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.event.MouseEvent; +import java.awt.geom.Area; +import java.awt.geom.Path2D; +import javax.annotation.Nullable; +import javax.swing.SwingUtilities; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.MapToolUtil; +import net.rptools.maptool.client.ScreenPoint; +import net.rptools.maptool.client.swing.colorpicker.ColorPicker; +import net.rptools.maptool.client.tool.ToolHelper; +import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; +import net.rptools.maptool.model.Zone; +import net.rptools.maptool.model.ZonePoint; +import net.rptools.maptool.model.drawing.Drawable; +import net.rptools.maptool.model.drawing.DrawableColorPaint; +import net.rptools.maptool.model.drawing.LineSegment; +import net.rptools.maptool.model.drawing.Pen; +import net.rptools.maptool.model.drawing.ShapeDrawable; + +public final class DrawingTool extends AbstractDrawingLikeTool { + private final String instructionKey; + private final String tooltipKey; + private final Strategy strategy; + + /** The current state of the tool. If {@code null}, nothing is being drawn right now. */ + private @Nullable StateT state; + + private ZonePoint currentPoint = new ZonePoint(0, 0); + private boolean centerOnOrigin = false; + + public DrawingTool(String instructionKey, String tooltipKey, Strategy strategy) { + this.instructionKey = instructionKey; + this.tooltipKey = tooltipKey; + this.strategy = strategy; + } + + @Override + public String getInstructions() { + return instructionKey; + } + + @Override + public String getTooltip() { + return tooltipKey; + } + + @Override + public boolean isAvailable() { + return MapTool.getPlayer().isGM(); + } + + @Override + protected boolean isLinearTool() { + return strategy.isLinear(); + } + + @Override + protected void attachTo(ZoneRenderer renderer) { + super.attachTo(renderer); + if (MapTool.getPlayer().isGM()) { + MapTool.getFrame() + .showControlPanel(MapTool.getFrame().getColorPicker(), getLayerSelectionDialog()); + } else { + MapTool.getFrame().showControlPanel(MapTool.getFrame().getColorPicker()); + } + renderer.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); + } + + @Override + protected void detachFrom(ZoneRenderer renderer) { + MapTool.getFrame().removeControlPanel(); + renderer.setCursor(Cursor.getDefaultCursor()); + + super.detachFrom(renderer); + } + + /** If currently drawing, stop and clear it. */ + @Override + protected void resetTool() { + if (state != null) { + state = null; + renderer.repaint(); + } else { + super.resetTool(); + } + } + + @Override + protected boolean isEraser(MouseEvent e) { + // Use the color picker as the default, but invert based on key state. + var inverted = super.isEraser(e); + boolean defaultValue = MapTool.getFrame().getColorPicker().isEraseSelected(); + if (inverted) { + defaultValue = !defaultValue; + } + return defaultValue; + } + + @Override + protected boolean isSnapToGrid(MouseEvent e) { + // Use the color picker as the default, but invert based on key state. + var inverted = super.isSnapToGrid(e); + boolean defaultValue = MapTool.getFrame().getColorPicker().isSnapSelected(); + if (inverted) { + // Invert from the color panel + defaultValue = !defaultValue; + } + return defaultValue; + } + + private boolean isBackgroundFill() { + return MapTool.getFrame().getColorPicker().isFillBackgroundSelected(); + } + + private boolean hasPaint(Pen pen) { + return pen.getForegroundMode() != Pen.MODE_TRANSPARENT + || pen.getBackgroundMode() != Pen.MODE_TRANSPARENT; + } + + private Pen getPen() { + Pen pen = new Pen(MapTool.getFrame().getPen()); + pen.setEraser(isEraser()); + + ColorPicker picker = MapTool.getFrame().getColorPicker(); + if (picker.isFillForegroundSelected()) { + pen.setForegroundMode(Pen.MODE_SOLID); + } else { + pen.setForegroundMode(Pen.MODE_TRANSPARENT); + } + if (picker.isFillBackgroundSelected()) { + pen.setBackgroundMode(Pen.MODE_SOLID); + } else { + pen.setBackgroundMode(Pen.MODE_TRANSPARENT); + } + pen.setSquareCap(picker.isSquareCapSelected()); + pen.setThickness(picker.getStrokeWidth()); + return pen; + } + + private Drawable toDrawable(Shape shape) { + if (shape instanceof Path2D path) { + if (isLinearTool()) { + // Preserve the path. Will be an unbroken path of straight segments. In the future we can + // look at handling more general paths. + var pen = getPen(); + return new LineSegment(pen.getThickness(), pen.getSquareCap(), path); + } else { + // The path describes an area. + return new ShapeDrawable(new Area(path), true); + } + } + + // All other shapes are solid and represented by ShapeDrawable. + return new ShapeDrawable(shape, true); + } + + private void submit(Shape shape) { + var pen = getPen(); + if (!hasPaint(pen)) { + return; + } + + Zone zone = getZone(); + var drawable = toDrawable(shape); + if (drawable.getBounds(zone) == null) { + return; + } + + if (MapTool.getPlayer().isGM()) { + drawable.setLayer(getSelectedLayer()); + } else { + drawable.setLayer(Zone.Layer.getDefaultPlayerLayer()); + } + + // Send new textures + MapToolUtil.uploadTexture(pen.getPaint()); + MapToolUtil.uploadTexture(pen.getBackgroundPaint()); + + // Tell the local/server to render the drawable. + MapTool.serverCommand().draw(zone.getId(), pen, drawable); + + // Allow it to be undone + zone.addDrawable(pen, drawable); + } + + @Override + public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { + Graphics2D g2 = (Graphics2D) g.create(); + g2.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); + g2.scale(renderer.getScale(), renderer.getScale()); + + if (state != null) { + // Linear tools are not filled until completed. + var result = strategy.getShape(state, currentPoint, centerOnOrigin, false); + if (result != null) { + var drawable = toDrawable(result.shape()); + + Pen pen = getPen(); + if (isEraser()) { + pen = new Pen(pen); + pen.setEraser(false); + pen.setPaint(new DrawableColorPaint(Color.white)); + pen.setBackgroundPaint(new DrawableColorPaint(Color.white)); + } + + drawable.draw(renderer.getZone(), g2, pen); + + // Measurements + var measurement = result.measurement(); + switch (measurement) { + case null -> {} + case Measurement.Rectangular rectangular -> { + var rectangle = rectangular.bounds(); + ToolHelper.drawBoxedMeasurement( + renderer, + g, + ScreenPoint.fromZonePoint(renderer, rectangle.getX(), rectangle.getY()), + ScreenPoint.fromZonePoint(renderer, rectangle.getMaxX(), rectangle.getMaxY())); + } + case Measurement.LineSegment lineSegment -> { + var p1 = + ScreenPoint.fromZonePoint( + renderer, lineSegment.p1().getX(), lineSegment.p1().getY()); + var p2 = + ScreenPoint.fromZonePoint( + renderer, lineSegment.p2().getX(), lineSegment.p2().getY()); + ToolHelper.drawMeasurement(renderer, g, p1, p2); + } + case Measurement.IsoRectangular isoRectangular -> { + var north = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.north().getX(), isoRectangular.north().getY()); + var west = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.west().getX(), isoRectangular.west().getY()); + var east = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.east().getX(), isoRectangular.east().getY()); + ToolHelper.drawIsoRectangleMeasurement(renderer, g, north, west, east); + } + } + } + } + + g2.dispose(); + } + + @Override + public void mouseDragged(MouseEvent e) { + if (state == null) { + // We're not doing anything, so delegate to default behaviour. + super.mouseDragged(e); + } else if (strategy.isFreehand()) { + // Extend the line. + setIsEraser(isEraser(e)); + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); // Pointless, but it doesn't hurt for consistency. + strategy.pushPoint(state, currentPoint); + renderer.repaint(); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + super.mouseMoved(e); + setIsEraser(isEraser(e)); + if (state != null) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + renderer.repaint(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + setIsEraser(isEraser(e)); + + if (SwingUtilities.isLeftMouseButton(e)) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + + if (state == null) { + state = strategy.startNewAtPoint(currentPoint); + } else if (!strategy.isFreehand()) { + var result = strategy.getShape(state, currentPoint, centerOnOrigin, isBackgroundFill()); + state = null; + if (result != null) { + submit(result.shape()); + } + } + renderer.repaint(); + } else if (state != null && !strategy.isFreehand()) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + strategy.pushPoint(state, currentPoint); + renderer.repaint(); + } + + super.mousePressed(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (strategy.isFreehand() && SwingUtilities.isLeftMouseButton(e)) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + var result = strategy.getShape(state, currentPoint, centerOnOrigin, isBackgroundFill()); + state = null; + if (result != null) { + submit(result.shape()); + } + } + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/FreehandTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/FreehandTool.java deleted file mode 100644 index 5f2840bbc4..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/FreehandTool.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import javax.swing.SwingUtilities; - -/** Tool for drawing freehand lines. */ -public class FreehandTool extends AbstractLineTool implements MouseMotionListener { - private static final long serialVersionUID = 3904963036442998837L; - - public FreehandTool() { - // Don't ever show measurement drawing with freehand tool - drawMeasurementDisabled = true; - } - - @Override - public String getTooltip() { - return "tool.freehand.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.freehand.instructions"; - } - - //// - // MOUSE LISTENER - @Override - public void mousePressed(MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e)) { - startLine(e); - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseReleased(MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e)) { - stopLine(e); - } - super.mouseReleased(e); - } - - //// - // MOUSE MOTION LISTENER - @Override - public void mouseDragged(java.awt.event.MouseEvent e) { - addPoint(e); - super.mouseDragged(e); - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/LineTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/LineTool.java deleted file mode 100644 index 6de6cd9cc0..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/LineTool.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Point; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import javax.swing.SwingUtilities; - -/** Tool for drawing freehand lines. */ -public class LineTool extends AbstractLineTool implements MouseMotionListener { - private static final long serialVersionUID = 3258132466219627316L; - private Point tempPoint; - - public LineTool() {} - - @Override - public String getTooltip() { - return "tool.line.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.line.instructions"; - } - - //// - // MOUSE LISTENER - @Override - public void mousePressed(MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e)) { - if (getLine() == null) { - startLine(e); - setIsEraser(isEraser(e)); - } else { - tempPoint = null; - stopLine(e); - } - } else if (getLine() != null) { - // Create a joint - tempPoint = null; - return; - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (getLine() == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - if (getLine() != null) { - if (tempPoint != null) { - removePoint(tempPoint); - } - tempPoint = addPoint(e); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java deleted file mode 100644 index 6c32668de8..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Ellipse2D; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.ScreenPoint; -import net.rptools.maptool.client.tool.Tool; -import net.rptools.maptool.client.tool.ToolHelper; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** - * @author drice - */ -public class OvalTool extends AbstractDrawingTool implements MouseMotionListener { - private static final long serialVersionUID = 3258413928311830323L; - - protected Rectangle oval; - private ZonePoint originPoint; - - public OvalTool() {} - - @Override - public String getTooltip() { - return "tool.oval.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.oval.instructions"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - if (oval != null) { - Pen pen = getPen(); - - if (pen.isEraser()) { - pen = new Pen(pen); - pen.setEraser(false); - pen.setPaint(new DrawableColorPaint(Color.white)); - pen.setBackgroundPaint(new DrawableColorPaint(Color.white)); - } - - paintTransformed( - g, - renderer, - new ShapeDrawable(new Ellipse2D.Double(oval.x, oval.y, oval.width, oval.height)), - pen); - - ToolHelper.drawBoxedMeasurement( - renderer, - g, - ScreenPoint.fromZonePoint(renderer, oval.x, oval.y), - ScreenPoint.fromZonePoint(renderer, oval.x + oval.width, oval.y + oval.height)); - } - } - - @Override - public void mousePressed(MouseEvent e) { - - if (SwingUtilities.isLeftMouseButton(e)) { - ZonePoint zp = getPoint(e); - - if (oval == null) { - originPoint = zp; - oval = createRect(zp, zp); - } else { - oval = createRect(originPoint, zp); - - // Draw from center if ALT is held down - if (e.isAltDown()) { - if (zp.x > originPoint.x) oval.x -= oval.width; - - if (zp.y > originPoint.y) oval.y -= oval.height; - - oval.width *= 2; - oval.height *= 2; - } - - completeDrawable( - getPen(), - new ShapeDrawable(new Ellipse2D.Double(oval.x, oval.y, oval.width, oval.height), true)); - oval = null; - } - - setIsEraser(isEraser(e)); - } - - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - - if (oval == null) { - super.mouseDragged(e); - } - } - - /* - * (non-Javadoc) - * - * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent) - */ - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - if (oval != null) { - - ZonePoint sp = getPoint(e); - - oval = createRect(originPoint, sp); - - // Draw from center if ALT is held down - if (e.isAltDown()) { - if (sp.x > originPoint.x) oval.x -= oval.width; - - if (sp.y > originPoint.y) oval.y -= oval.height; - - oval.width *= 2; - oval.height *= 2; - } - - renderer.repaint(); - } - } - - /** - * @see Tool#resetTool() - */ - @Override - protected void resetTool() { - - if (oval != null) { - oval = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTool.java deleted file mode 100644 index d7f3156669..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTool.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Point; -import java.awt.Polygon; -import java.awt.event.MouseMotionListener; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.LineSegment; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** Tool for drawing freehand lines. */ -public class PolygonTool extends LineTool implements MouseMotionListener { - private static final long serialVersionUID = 3258132466219627316L; - - public PolygonTool() {} - - @Override - public String getTooltip() { - return "tool.poly.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.poly.instructions"; - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - LineSegment line = (LineSegment) drawable; - super.completeDrawable(pen, new ShapeDrawable(getPolygon(line))); - } - - @Override - protected Polygon getPolygon(LineSegment line) { - Polygon polygon = new Polygon(); - for (Point point : line.getPoints()) { - polygon.addPoint(point.x, point.y); - } - return polygon; - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTool.java deleted file mode 100644 index aa68e050a2..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTool.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.ScreenPoint; -import net.rptools.maptool.client.tool.ToolHelper; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** - * @author drice - */ -public class RectangleTool extends AbstractDrawingTool implements MouseMotionListener { - private static final long serialVersionUID = 3258413928311830323L; - - protected Rectangle rectangle; - protected ZonePoint originPoint; - - public RectangleTool() {} - - @Override - public String getInstructions() { - return "tool.rect.instructions"; - } - - @Override - public String getTooltip() { - return "tool.rect.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - if (rectangle != null) { - Pen pen = getPen(); - if (pen.isEraser()) { - pen = new Pen(pen); - pen.setEraser(false); - pen.setPaint(new DrawableColorPaint(Color.white)); - pen.setBackgroundPaint(new DrawableColorPaint(Color.white)); - } - paintTransformed(g, renderer, new ShapeDrawable(rectangle, false), pen); - ToolHelper.drawBoxedMeasurement( - renderer, - g, - ScreenPoint.fromZonePoint(renderer, rectangle.x, rectangle.y), - ScreenPoint.fromZonePoint( - renderer, rectangle.x + rectangle.width, rectangle.y + rectangle.height)); - } - } - - @Override - public void mousePressed(MouseEvent e) { - ZonePoint zp = getPoint(e); - if (SwingUtilities.isLeftMouseButton(e)) { - if (rectangle == null) { - originPoint = zp; - rectangle = createRect(originPoint, originPoint); - } else { - rectangle = createRect(originPoint, zp); - - if (rectangle.width == 0 || rectangle.height == 0) { - rectangle = null; - renderer.repaint(); - return; - } - // Draw Rectangle with initial point as Center - if (e.isAltDown()) { - if (zp.x > originPoint.x) rectangle.x -= rectangle.width; - - if (zp.y > originPoint.y) rectangle.y -= rectangle.height; - - rectangle.width *= 2; - rectangle.height *= 2; - } - // System.out.println("Adding Rectangle to zone: " + rectangle); - completeDrawable(getPen(), new ShapeDrawable(rectangle, false)); - rectangle = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (rectangle == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - if (rectangle != null) { - ZonePoint p = getPoint(e); - rectangle = createRect(originPoint, p); - - // Draw Rectangle with initial point as Center - if (e.isAltDown()) { - if (p.x > originPoint.x) rectangle.x -= rectangle.width; - - if (p.y > originPoint.y) rectangle.y -= rectangle.height; - - rectangle.width *= 2; - rectangle.height *= 2; - } - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (rectangle != null) { - rectangle = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java index 21405ce336..84c66e7124 100644 --- a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java @@ -264,12 +264,33 @@ private OptionPanel createDrawPanel() { panel .add(DeleteDrawingTool.class) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_DELETE)); - panel.add(FreehandTool.class).setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_FREEHAND)); - panel.add(LineTool.class).setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_LINE)); - panel.add(RectangleTool.class).setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_BOX)); - panel.add(OvalTool.class).setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_OVAL)); + + panel + .addTool( + new DrawingTool<>( + "tool.freehand.instructions", "tool.freehand.tooltip", new PolyLineStrategy(true))) + .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_FREEHAND)); + panel + .addTool( + new DrawingTool<>( + "tool.line.instructions", "tool.line.tooltip", new PolyLineStrategy(false))) + .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_LINE)); + panel + .addTool( + new DrawingTool<>( + "tool.rect.instructions", "tool.rect.tooltip", new RectangleStrategy())) + .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_BOX)); + panel + .addTool( + new DrawingTool<>("tool.oval.instructions", "tool.oval.tooltip", new OvalStrategy())) + .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_OVAL)); panel.add(TextTool.class).setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_TEXT)); - panel.add(DiamondTool.class).setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_DIAMOND)); + panel + .addTool( + new DrawingTool<>( + "tool.rect.instructions", "tool.isorectangle.tooltip", new IsoRectangleStrategy())) + .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_DIAMOND)); + return panel; } diff --git a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java index 292c8fc78b..f6624b773b 100644 --- a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java +++ b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java @@ -292,7 +292,7 @@ public void actionPerformed(ActionEvent e) { Shape s = a; Pen newPen = new Pen(elementUnderMouse.getPen()); if (elementUnderMouse.getDrawable() instanceof LineSegment) newPen = invertPen(newPen); - DrawnElement de = new DrawnElement(new ShapeDrawable(s), newPen); + DrawnElement de = new DrawnElement(new ShapeDrawable(s, true), newPen); de.getDrawable().setLayer(elementUnderMouse.getDrawable().getLayer()); MapTool.serverCommand().draw(renderer.getZone().getId(), newPen, de.getDrawable()); MapTool.getFrame().updateDrawTree(); diff --git a/src/main/java/net/rptools/maptool/model/drawing/LineSegment.java b/src/main/java/net/rptools/maptool/model/drawing/LineSegment.java index 68c268e396..7b01a4c000 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/LineSegment.java +++ b/src/main/java/net/rptools/maptool/model/drawing/LineSegment.java @@ -21,7 +21,10 @@ import java.awt.Rectangle; import java.awt.geom.Area; import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; import javax.annotation.Nonnull; @@ -63,6 +66,28 @@ public LineSegment(LineSegment other) { } } + public LineSegment(float width, boolean squareCap, Path2D path) { + this.width = width; + this.squareCap = squareCap; + + // Assume an unbroken line string. + double[] coordinates = new double[6]; + for (var it = path.getPathIterator(null); !it.isDone(); it.next()) { + var type = it.currentSegment(coordinates); + // type will be SEG_MOVETO or SEG_LINETO or SEG_CLOSE. The first two mean the same to + // us, while the latter ends the line. + if (type == PathIterator.SEG_CLOSE) { + // Should not be possible to be empty, but can't hurt to check. + if (!points.isEmpty()) { + addPoint(points.getFirst()); + } + break; + } else { + addPoint((int) coordinates[0], (int) coordinates[1]); + } + } + } + @Override public Drawable copy() { return new LineSegment(this); @@ -78,16 +103,31 @@ private Object readResolve() { } /** - * Manipulate the points by calling {@link #getPoints} and then adding {@link Point} objects to - * the returned {@link List}. + * Add a point to the line. + * + * @param x + * @param y + */ + public void addPoint(int x, int y) { + area = null; + points.add(new Point(x, y)); + } + + /** + * Add a point to the line. * + * @param point + * @see #addPoint(int, int) + */ + public void addPoint(Point point) { + addPoint(point.x, point.y); + } + + /** * @return the list of point */ public List getPoints() { - // This is really, really ugly, but we need to flush the area on any change to the shape - // and typically the reason for calling this method is to change the list - area = null; - return points; + return Collections.unmodifiableList(points); } @Override @@ -108,14 +148,14 @@ public DrawableDto toDto() { if (getName() != null) dto.setName(StringValue.of(getName())); - getPoints().forEach(p -> dto.addPoints(Mapper.map(p))); + points.forEach(p -> dto.addPoints(Mapper.map(p))); return DrawableDto.newBuilder().setLineSegment(dto).build(); } public static LineSegment fromDto(LineSegmentDrawableDto dto) { var id = GUID.valueOf(dto.getId()); var drawable = new LineSegment(id, dto.getWidth(), dto.getSquareCap()); - var points = drawable.getPoints(); + var points = drawable.points; var pointDtos = dto.getPointsList(); pointDtos.forEach(p -> points.add(Mapper.map(p))); if (dto.hasName()) { diff --git a/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java b/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java index fede411fa6..aa91dda1b2 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java +++ b/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java @@ -44,10 +44,6 @@ public ShapeDrawable(Shape shape, boolean useAntiAliasing) { this.useAntiAliasing = useAntiAliasing; } - public ShapeDrawable(Shape shape) { - this(shape, true); - } - public ShapeDrawable(ShapeDrawable other) { super(other); this.useAntiAliasing = other.useAntiAliasing; From 3717c627fede321e748e1a7dcfebed845f8b576c Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 15:32:56 -0700 Subject: [PATCH 144/146] Deprecate now-unused drawing types `Oval`, `Rectangle`, and `Cross` no longer serve a purpose and so have been deprecated. It is possible for existing campaigns to have these serialized in them, so we are keeping the structure around. However, each one will `readResolve()` itself into a modern type. Also be clear about the set of supported types in `ShapeDrawable`. The new `ShapeDrawable#getShapeTypeName()` outputs one of `"Rectangle"`, `"Oval"`, `"Polygon"`, `"Area"`, or `"Unknown"`, which callers can use to make meaningful decisions about the general type of shape wrapped in the drawable. This matters especially for ellipses, where several places refers to the simple name of `Ellipse2D.Float`, which is just `"Float"`. Now they refer to `"Oval"`, or use proper type checks where needed. --- .../client/functions/DrawingFunctions.java | 19 +-- .../ui/drawpanel/DrawPanelPopupMenu.java | 14 +- .../drawpanel/DrawPanelTreeCellRenderer.java | 24 +-- .../rptools/maptool/model/drawing/Cross.java | 150 ++++-------------- .../maptool/model/drawing/Drawable.java | 3 - .../rptools/maptool/model/drawing/Oval.java | 110 +++---------- .../maptool/model/drawing/Rectangle.java | 139 +++------------- .../maptool/model/drawing/ShapeDrawable.java | 21 +++ src/main/proto/drawing_dto.proto | 28 ---- .../rptools/maptool/language/i18n.properties | 3 +- 10 files changed, 123 insertions(+), 388 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java b/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java index f60cfafb37..218fb799f8 100644 --- a/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java @@ -17,6 +17,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import java.awt.Point; +import java.awt.geom.Ellipse2D; import java.awt.geom.PathIterator; import java.math.BigDecimal; import java.util.List; @@ -274,13 +275,8 @@ private JsonObject boundsToJSON(Zone map, AbstractDrawing d) { private String getDrawbleType(AbstractDrawing d) { if (d instanceof LineSegment) { return "Line"; - } else if (d instanceof ShapeDrawable) { - String shape = ((ShapeDrawable) d).getShape().getClass().getSimpleName(); - if ("Float".equalsIgnoreCase(shape)) { - return "Oval"; - } else { - return shape; - } + } else if (d instanceof ShapeDrawable sd) { + return sd.getShapeTypeName(); } else if (d instanceof DrawablesGroup) { return "Group"; } else { @@ -299,14 +295,15 @@ private JsonArray pathToJSON(AbstractDrawing d) { pinfo.add(info); } return pinfo; - } else if (d instanceof ShapeDrawable) { - String shape = ((ShapeDrawable) d).getShape().getClass().getSimpleName(); - if ("Float".equalsIgnoreCase(shape)) { + } else if (d instanceof ShapeDrawable sd) { + var shape = sd.getShape(); + if (shape instanceof Ellipse2D) { + // We don't support converting ellipses to path. return new JsonArray(); } else { // Convert shape into path JsonArray pinfo = new JsonArray(); - final PathIterator pathIter = ((ShapeDrawable) d).getShape().getPathIterator(null); + final PathIterator pathIter = shape.getPathIterator(null); float[] coords = new float[6]; JsonObject lastinfo = new JsonObject(); while (!pathIter.isDone()) { diff --git a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java index f6624b773b..b9317f9c64 100644 --- a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java +++ b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java @@ -20,6 +20,7 @@ import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; import java.awt.geom.Path2D; import java.util.ArrayList; import java.util.Iterator; @@ -536,11 +537,14 @@ private boolean hasPath(Set selectedDrawSet) { * @return boolean */ private boolean hasPath(DrawnElement drawnElement) { - if (drawnElement == null) return false; - if (drawnElement.getDrawable() instanceof LineSegment) return true; - if (drawnElement.getDrawable() instanceof ShapeDrawable) { - ShapeDrawable sd = (ShapeDrawable) drawnElement.getDrawable(); - return "Float".equalsIgnoreCase(sd.getShape().getClass().getSimpleName()) == false; + if (drawnElement == null) { + return false; + } + if (drawnElement.getDrawable() instanceof LineSegment) { + return true; + } + if (drawnElement.getDrawable() instanceof ShapeDrawable sd) { + return !(sd.getShape() instanceof Ellipse2D); } return false; } diff --git a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelTreeCellRenderer.java b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelTreeCellRenderer.java index c1f6197dde..e87f5a8497 100644 --- a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelTreeCellRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelTreeCellRenderer.java @@ -51,28 +51,20 @@ public Component getTreeCellRendererComponent( String text = ""; this.row = row; - if (value instanceof DrawnElement) { - String key = "panel.DrawExplorer.Unknown.Shape"; - DrawnElement de = (DrawnElement) value; + if (value instanceof DrawnElement de) { text = de.getDrawable().toString(); if (de.getDrawable() instanceof DrawablesGroup) { text = I18N.getString("panel.DrawExplorer.group"); - } else if (de.getDrawable() instanceof ShapeDrawable) { - ShapeDrawable sd = (ShapeDrawable) de.getDrawable(); - key = - String.format( - "panel.DrawExplorer.%s.%s", - sd.getClass().getSimpleName(), sd.getShape().getClass().getSimpleName()); + } else if (de.getDrawable() instanceof ShapeDrawable sd) { + var key = String.format("panel.DrawExplorer.ShapeDrawable.%s", sd.getShapeTypeName()); text = I18N.getText(key, sd.getBounds().width, sd.getBounds().height); setLeafIcon(setDrawPanelIcon(key, de.getPen().isEraser())); - } else if (de.getDrawable() instanceof LineSegment) { - LineSegment ls = (LineSegment) de.getDrawable(); - key = String.format("panel.DrawExplorer.%s.Line", ls.getClass().getSimpleName()); + } else if (de.getDrawable() instanceof LineSegment ls) { + var key = "panel.DrawExplorer.LineSegment.Line"; text = I18N.getText(key, ls.getPoints().size(), de.getPen().getThickness()); setLeafIcon(setDrawPanelIcon(key, de.getPen().isEraser())); - } else if (de.getDrawable() instanceof AbstractTemplate) { - AbstractTemplate at = (AbstractTemplate) de.getDrawable(); - key = String.format("panel.DrawExplorer.Template.%s", at.getClass().getSimpleName()); + } else if (de.getDrawable() instanceof AbstractTemplate at) { + var key = String.format("panel.DrawExplorer.Template.%s", at.getClass().getSimpleName()); text = I18N.getText(key, at.getRadius()); setLeafIcon(setDrawPanelIcon(key, de.getPen().isEraser())); } @@ -116,7 +108,7 @@ private Icon setDrawPanelIcon(String key, boolean eraser) { case "panel.DrawExplorer.ShapeDrawable.Polygon": if (eraser) return RessourceManager.getSmallIcon(Icons.DRAWPANEL_POLYGON_ERASE); else return RessourceManager.getSmallIcon(Icons.DRAWPANEL_POLYGON_DRAW); - case "panel.DrawExplorer.ShapeDrawable.Float": + case "panel.DrawExplorer.ShapeDrawable.Oval": if (eraser) return RessourceManager.getSmallIcon(Icons.DRAWPANEL_ELLIPSE_ERASE); else return RessourceManager.getSmallIcon(Icons.DRAWPANEL_ELLIPSE_DRAW); case "panel.DrawExplorer.ShapeDrawable.Rectangle": diff --git a/src/main/java/net/rptools/maptool/model/drawing/Cross.java b/src/main/java/net/rptools/maptool/model/drawing/Cross.java index 58ed13e4b4..f9d96cc430 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/Cross.java +++ b/src/main/java/net/rptools/maptool/model/drawing/Cross.java @@ -14,131 +14,35 @@ */ package net.rptools.maptool.model.drawing; -import com.google.protobuf.StringValue; -import java.awt.Graphics2D; import java.awt.Point; -import java.awt.RenderingHints; -import java.awt.geom.Area; -import javax.annotation.Nonnull; +import java.awt.geom.Path2D; +import java.io.Serial; +import java.io.Serializable; import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.server.Mapper; -import net.rptools.maptool.server.proto.drawing.CrossDrawableDto; -import net.rptools.maptool.server.proto.drawing.DrawableDto; -/** An Cross */ -public class Cross extends AbstractDrawing { - protected Point startPoint; - protected Point endPoint; - private transient java.awt.Rectangle bounds; - - public Cross(int startX, int startY, int endX, int endY) { - startPoint = new Point(startX, startY); - endPoint = new Point(endX, endY); - } - - public Cross(GUID id, int startX, int startY, int endX, int endY) { - super(id); - startPoint = new Point(startX, startY); - endPoint = new Point(endX, endY); - } - - public Cross(Cross other) { - super(other); - - this.startPoint = new Point(other.startPoint); - this.endPoint = new Point(other.endPoint); - } - - @Override - public Drawable copy() { - return new Cross(this); - } - - @Override - public @Nonnull Area getArea(Zone zone) { - return new Area(getBounds(zone)); - } - - @Override - public DrawableDto toDto() { - var dto = - CrossDrawableDto.newBuilder() - .setId(getId().toString()) - .setLayer(getLayer().name()) - .setStartPoint(Mapper.map(getStartPoint())) - .setEndPoint(Mapper.map(getEndPoint())); - - if (getName() != null) dto.setName(StringValue.of(getName())); - - return DrawableDto.newBuilder().setCrossDrawable(dto).build(); - } - - public static Cross fromDto(CrossDrawableDto dto) { - var id = GUID.valueOf(dto.getId()); - var startPoint = dto.getStartPoint(); - var endPoint = dto.getEndPoint(); - var drawable = - new Cross(id, startPoint.getX(), startPoint.getY(), endPoint.getX(), endPoint.getY()); - if (dto.hasName()) { - drawable.setName(dto.getName().getValue()); - } - drawable.setLayer(Zone.Layer.valueOf(dto.getLayer())); - return drawable; - } - - @Override - public java.awt.Rectangle getBounds(Zone zone) { - if (bounds == null) { - int x = Math.min(startPoint.x, endPoint.x); - int y = Math.min(startPoint.y, endPoint.y); - int width = Math.abs(endPoint.x - startPoint.x); - int height = Math.abs(endPoint.y - startPoint.y); - - bounds = new java.awt.Rectangle(x, y, width, height); - } - - return bounds; - } - - public Point getStartPoint() { - return startPoint; - } - - public Point getEndPoint() { - return endPoint; - } - - @Override - protected void draw(Zone zone, Graphics2D g) { - - int minX = Math.min(startPoint.x, endPoint.x); - int minY = Math.min(startPoint.y, endPoint.y); - - int width = Math.abs(startPoint.x - endPoint.x); - int height = Math.abs(startPoint.y - endPoint.y); - - Object oldAA = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - // g.drawRect(minX, minY, width, height); - - g.drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y); - g.drawLine(startPoint.x, endPoint.y, endPoint.x, startPoint.y); - - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAA); - } - - @Override - protected void drawBackground(Zone zone, Graphics2D g) { - int minX = Math.min(startPoint.x, endPoint.x); - int minY = Math.min(startPoint.y, endPoint.y); - - int width = Math.abs(startPoint.x - endPoint.x); - int height = Math.abs(startPoint.y - endPoint.y); - - Object oldAA = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - g.fillRect(minX, minY, width, height); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAA); +/** + * An X-shaped cross + * + * @deprecated This is a legacy class not currently in use. It is kept here in case it has been + * serialized in any existing campaigns. It used to extend {@link AbstractDrawing} but is now * + * just a holder for data and will replace itself with a {@link ShapeDrawable}. + */ +@Deprecated +public final class Cross implements Serializable { + private GUID id; + private String layer; + private String name; + private Point startPoint; + private Point endPoint; + + @Serial + private Object readResolve() { + var path = new Path2D.Double(); + path.moveTo(startPoint.x, startPoint.y); + path.lineTo(endPoint.x, endPoint.y); + path.moveTo(startPoint.x, endPoint.y); + path.lineTo(endPoint.x, startPoint.y); + + return new ShapeDrawable(id, path, false); } } diff --git a/src/main/java/net/rptools/maptool/model/drawing/Drawable.java b/src/main/java/net/rptools/maptool/model/drawing/Drawable.java index 034fa9fcc2..a2ee068b0b 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/Drawable.java +++ b/src/main/java/net/rptools/maptool/model/drawing/Drawable.java @@ -48,9 +48,6 @@ public interface Drawable { static Drawable fromDto(DrawableDto drawableDto) { return switch (drawableDto.getDrawableTypeCase()) { case SHAPE_DRAWABLE -> ShapeDrawable.fromDto(drawableDto.getShapeDrawable()); - case RECTANGLE_DRAWABLE -> Rectangle.fromDto(drawableDto.getRectangleDrawable()); - case OVAL_DRAWABLE -> Oval.fromDto(drawableDto.getOvalDrawable()); - case CROSS_DRAWABLE -> Cross.fromDto(drawableDto.getCrossDrawable()); case DRAWN_LABEL -> DrawnLabel.fromDto(drawableDto.getDrawnLabel()); case LINE_SEGMENT -> LineSegment.fromDto(drawableDto.getLineSegment()); case DRAWABLES_GROUP -> DrawablesGroup.fromDto(drawableDto.getDrawablesGroup()); diff --git a/src/main/java/net/rptools/maptool/model/drawing/Oval.java b/src/main/java/net/rptools/maptool/model/drawing/Oval.java index 4b16a8425f..f785431961 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/Oval.java +++ b/src/main/java/net/rptools/maptool/model/drawing/Oval.java @@ -14,93 +14,33 @@ */ package net.rptools.maptool.model.drawing; -import com.google.protobuf.StringValue; -import java.awt.Graphics2D; -import java.awt.geom.Area; +import java.awt.Point; import java.awt.geom.Ellipse2D; -import javax.annotation.Nonnull; +import java.io.Serial; +import java.io.Serializable; import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.server.Mapper; -import net.rptools.maptool.server.proto.drawing.DrawableDto; -import net.rptools.maptool.server.proto.drawing.OvalDrawableDto; -/** An oval. */ -public class Oval extends Rectangle { - /** - * @param x the x offset - * @param y the y offset - * @param width the width of the oval - * @param height the height of the oval - */ - public Oval(int x, int y, int width, int height) { - super(x, y, width, height); - } - - public Oval(GUID id, int x, int y, int width, int height) { - super(id, x, y, width, height); - } - - public Oval(Oval other) { - super(other); - } - - @Override - public Drawable copy() { - return new Oval(this); - } - - @Override - protected void draw(Zone zone, Graphics2D g) { - int minX = Math.min(startPoint.x, endPoint.x); - int minY = Math.min(startPoint.y, endPoint.y); - - int width = Math.abs(startPoint.x - endPoint.x); - int height = Math.abs(startPoint.y - endPoint.y); - - g.drawOval(minX, minY, width, height); - } - - @Override - protected void drawBackground(Zone zone, Graphics2D g) { - int minX = Math.min(startPoint.x, endPoint.x); - int minY = Math.min(startPoint.y, endPoint.y); - - int width = Math.abs(startPoint.x - endPoint.x); - int height = Math.abs(startPoint.y - endPoint.y); - - g.fillOval(minX, minY, width, height); - } - - @Override - public @Nonnull Area getArea(Zone zone) { - java.awt.Rectangle r = getBounds(zone); - return new Area(new Ellipse2D.Double(r.x, r.y, r.width, r.height)); - } - - public DrawableDto toDto() { - var dto = - OvalDrawableDto.newBuilder() - .setId(getId().toString()) - .setLayer(getLayer().name()) - .setStartPoint(Mapper.map(getStartPoint())) - .setEndPoint(Mapper.map(getEndPoint())); - - if (getName() != null) dto.setName(StringValue.of(getName())); - - return DrawableDto.newBuilder().setOvalDrawable(dto).build(); - } - - public static Oval fromDto(OvalDrawableDto dto) { - var id = GUID.valueOf(dto.getId()); - var startPoint = dto.getStartPoint(); - var endPoint = dto.getEndPoint(); - var drawable = - new Oval(id, startPoint.getX(), startPoint.getY(), endPoint.getX(), endPoint.getY()); - if (dto.hasName()) { - drawable.setName(dto.getName().getValue()); - } - drawable.setLayer(Zone.Layer.valueOf(dto.getLayer())); - return drawable; +/** + * An oval. + * + * @deprecated This is a legacy class not currently in use. It is kept here in case it has been + * serialized in any existing campaigns. It used to extend {@link Rectangle} and {@link + * AbstractDrawing} but is now just a holder for data and will replace itself with a {@link + * ShapeDrawable}. + */ +@Deprecated +public final class Oval implements Serializable { + private GUID id; + private String layer; + private String name; + private Point startPoint; + private Point endPoint; + + @Serial + private Object readResolve() { + var ellipse = + new Ellipse2D.Double( + startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y - startPoint.y); + return new ShapeDrawable(id, ellipse, true); } } diff --git a/src/main/java/net/rptools/maptool/model/drawing/Rectangle.java b/src/main/java/net/rptools/maptool/model/drawing/Rectangle.java index b0dbfa771e..5ac1c830e6 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/Rectangle.java +++ b/src/main/java/net/rptools/maptool/model/drawing/Rectangle.java @@ -14,124 +14,31 @@ */ package net.rptools.maptool.model.drawing; -import com.google.protobuf.StringValue; -import java.awt.Graphics2D; import java.awt.Point; -import java.awt.RenderingHints; -import java.awt.geom.Area; -import javax.annotation.Nonnull; +import java.io.Serial; +import java.io.Serializable; import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.server.Mapper; -import net.rptools.maptool.server.proto.drawing.DrawableDto; -import net.rptools.maptool.server.proto.drawing.RectangleDrawableDto; -/** An rectangle */ -public class Rectangle extends AbstractDrawing { - protected Point startPoint; - protected Point endPoint; - private transient java.awt.Rectangle bounds; - - public Rectangle(GUID id, int startX, int startY, int endX, int endY) { - super(id); - startPoint = new Point(startX, startY); - endPoint = new Point(endX, endY); - } - - public Rectangle(int startX, int startY, int endX, int endY) { - startPoint = new Point(startX, startY); - endPoint = new Point(endX, endY); - } - - public Rectangle(Rectangle other) { - super(other); - this.startPoint = new Point(other.startPoint); - this.endPoint = new Point(other.endPoint); - } - - @Override - public Drawable copy() { - return new Rectangle(this); - } - - @Override - public @Nonnull Area getArea(Zone zone) { - return new Area(getBounds(zone)); - } - - @Override - public DrawableDto toDto() { - var dto = - RectangleDrawableDto.newBuilder() - .setId(getId().toString()) - .setLayer(getLayer().name()) - .setStartPoint(Mapper.map(getStartPoint())) - .setEndPoint(Mapper.map(getEndPoint())); - - if (getName() != null) dto.setName(StringValue.of(getName())); - - return DrawableDto.newBuilder().setRectangleDrawable(dto).build(); - } - - public static Rectangle fromDto(RectangleDrawableDto dto) { - var id = GUID.valueOf(dto.getId()); - var startPoint = dto.getStartPoint(); - var endPoint = dto.getEndPoint(); - var drawable = - new Rectangle(id, startPoint.getX(), startPoint.getY(), endPoint.getX(), endPoint.getY()); - if (dto.hasName()) { - drawable.setName(dto.getName().getValue()); - } - drawable.setLayer(Zone.Layer.valueOf(dto.getLayer())); - return drawable; - } - - @Override - public java.awt.Rectangle getBounds(Zone zone) { - if (bounds == null) { - int x = Math.min(startPoint.x, endPoint.x); - int y = Math.min(startPoint.y, endPoint.y); - int width = Math.abs(endPoint.x - startPoint.x); - int height = Math.abs(endPoint.y - startPoint.y); - - bounds = new java.awt.Rectangle(x, y, width, height); - } - return bounds; - } - - public Point getStartPoint() { - return startPoint; - } - - public Point getEndPoint() { - return endPoint; - } - - @Override - protected void draw(Zone zone, Graphics2D g) { - int minX = Math.min(startPoint.x, endPoint.x); - int minY = Math.min(startPoint.y, endPoint.y); - - int width = Math.abs(startPoint.x - endPoint.x); - int height = Math.abs(startPoint.y - endPoint.y); - - Object oldAA = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - g.drawRect(minX, minY, width, height); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAA); - } - - @Override - protected void drawBackground(Zone zone, Graphics2D g) { - int minX = Math.min(startPoint.x, endPoint.x); - int minY = Math.min(startPoint.y, endPoint.y); - - int width = Math.abs(startPoint.x - endPoint.x); - int height = Math.abs(startPoint.y - endPoint.y); - - Object oldAA = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - g.fillRect(minX, minY, width, height); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAA); +/** + * A rectangle. + * + * @deprecated This is a legacy class not currently in use. It is kept here in case it has been + * serialized in any existing campaigns. It used to extend {@link AbstractDrawing} but is now + * just a holder for data and will replace itself with a {@link ShapeDrawable}. + */ +@Deprecated +public final class Rectangle implements Serializable { + private GUID id; + private String layer; + private String name; + private Point startPoint; + private Point endPoint; + + @Serial + private Object readResolve() { + var rectangle = + new java.awt.Rectangle( + startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y - startPoint.y); + return new ShapeDrawable(this.id, rectangle, false); } } diff --git a/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java b/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java index aa91dda1b2..6a2fae623f 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java +++ b/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java @@ -17,9 +17,11 @@ import com.google.protobuf.StringValue; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; import java.awt.geom.RectangularShape; import javax.annotation.Nonnull; import net.rptools.maptool.model.GUID; @@ -62,6 +64,25 @@ public Drawable copy() { return new ShapeDrawable(this); } + /** + * Get a descriptive name for the type of shape wrapped by this {@code ShapeDrawable}. + * + *

Note: do not use this method for type checks. It is only useful for producing human-readable + * text, including by building translation keys. If you need type checking, using {@link + * #getShape()} along with `instanceof`! + * + * @return The type of shape contained in this drawable. + */ + public String getShapeTypeName() { + return switch (shape) { + case Rectangle ignored -> "Rectangle"; + case Ellipse2D ignored -> "Oval"; + case Polygon ignored -> "Polygon"; + case Area ignored -> "Area"; + default -> "Unknown"; + }; + } + public boolean getUseAntiAliasing() { return useAntiAliasing; } diff --git a/src/main/proto/drawing_dto.proto b/src/main/proto/drawing_dto.proto index 37815323de..f77e9428b3 100644 --- a/src/main/proto/drawing_dto.proto +++ b/src/main/proto/drawing_dto.proto @@ -36,9 +36,6 @@ message PenDto { message DrawableDto { oneof drawable_type { ShapeDrawableDto shape_drawable = 1; - RectangleDrawableDto rectangle_drawable = 2; - OvalDrawableDto oval_drawable = 3; - CrossDrawableDto cross_drawable = 4; DrawnLabelDto drawn_label = 5; LineSegmentDrawableDto line_segment = 6; DrawablesGroupDto drawables_group = 7; @@ -156,31 +153,6 @@ message DrawnLabelDto { string font = 6; } -message RectangleDrawableDto { - string id = 1; - string layer = 2; - google.protobuf.StringValue name = 3; - IntPointDto start_point = 4; - IntPointDto end_point = 5; -} - -message OvalDrawableDto { - string id = 1; - string layer = 2; - google.protobuf.StringValue name = 3; - IntPointDto start_point = 4; - IntPointDto end_point = 5; -} - -message CrossDrawableDto { - string id = 1; - string layer = 2; - google.protobuf.StringValue name = 3; - IntPointDto start_point = 4; - IntPointDto end_point = 5; -} - - message ShapeDrawableDto { string id = 1; string layer = 2; diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 92cf3b9bab..7e685d9436 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2489,9 +2489,10 @@ panel.DrawExplorer = Draw Explorer panel.DrawExplorer.description = Dockable window for managing drawings. panel.DrawExplorer.LineSegment.Line = points({0}) pen width({1}) panel.DrawExplorer.ShapeDrawable.Area = width({0}) height({1}) -panel.DrawExplorer.ShapeDrawable.Float = width({0}) height({1}) +panel.DrawExplorer.ShapeDrawable.Oval = width({0}) height({1}) panel.DrawExplorer.ShapeDrawable.Polygon = width({0}) height({1}) panel.DrawExplorer.ShapeDrawable.Rectangle = width({0}) height({1}) +panel.DrawExplorer.ShapeDrawable.Unknown = width({0}) height({1}) panel.DrawExplorer.Template.BlastTemplate = Blast ({0}) panel.DrawExplorer.Template.BurstTemplate = Burst ({0}) panel.DrawExplorer.Template.ConeTemplate = Cone ({0}) From b55ccfb9047e5c5a75fe621a8bdb7428934b47ec Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 18:09:01 -0700 Subject: [PATCH 145/146] Clean up - Remove unused `ToolHelper` methods. - Move measurement rendering into `AbstractDrawingLikeTool` so it isn't duplicated. - Rename `AbstractDrawingTool` to `AbstractTemplateTool` since that is all it does now. Also remove anything that isn't used anymore. --- .../maptool/client/tool/ToolHelper.java | 77 ---- .../tool/drawing/AbstractDrawingLikeTool.java | 38 ++ .../tool/drawing/AbstractDrawingTool.java | 328 ------------------ .../tool/drawing/AbstractTemplateTool.java | 150 ++++++++ .../client/tool/drawing/DrawingTool.java | 36 +- .../client/tool/drawing/ExposeTool.java | 36 +- .../tool/drawing/RadiusCellTemplateTool.java | 4 +- .../tool/drawing/RadiusTemplateTool.java | 4 +- 8 files changed, 194 insertions(+), 479 deletions(-) delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/AbstractTemplateTool.java diff --git a/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java b/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java index 06422e4b60..532a3cfa56 100644 --- a/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java +++ b/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java @@ -17,11 +17,8 @@ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; -import java.awt.Shape; import java.awt.event.ActionEvent; -import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; -import java.awt.geom.PathIterator; import java.text.NumberFormat; import javax.swing.AbstractAction; import javax.swing.SwingUtilities; @@ -78,72 +75,6 @@ public static void drawIsoRectangleMeasurement( } } - public static void drawDiamondMeasurement(ZoneRenderer renderer, Graphics2D g, Shape diamond) { - double[] north = null; - double[] west = null; - double[] east = null; - PathIterator path = diamond.getPathIterator(getPaintTransform(renderer)); - while (!path.isDone()) { - double[] coords = new double[2]; - int segType = path.currentSegment(coords); - if (segType != PathIterator.SEG_CLOSE) { - if (north == null) { - north = coords; - } - if (west == null) { - west = coords; - } - if (east == null) { - east = coords; - } - if (coords[1] < north[1]) { - north = coords; - } - if (coords[0] < west[0]) { - west = coords; - } - if (coords[0] > east[0]) { - east = coords; - } - } - path.next(); - } - // Measure - int nx = (int) north[0]; - int ny = (int) north[1]; - int ex = (int) east[0]; - int ey = (int) east[1]; - int wx = (int) west[0]; - int wy = (int) west[1]; - if (g != null) { - g.setColor(Color.white); - g.setStroke(new BasicStroke(3)); - g.drawLine(nx, ny - 20, nx, ny - 10); - g.drawLine(nx, ny - 15, ex, ey - 15); - g.drawLine(ex, ey - 20, ex, ey - 10); - g.drawLine(nx, ny - 15, wx, wy - 15); - g.drawLine(wx, wy - 20, wx, wy - 10); - g.setColor(Color.black); - g.setStroke(new BasicStroke(1)); - g.drawLine(nx, ny - 20, nx, ny - 10); - g.drawLine(nx, ny - 15, ex, ey - 15); - g.drawLine(ex, ey - 20, ex, ey - 10); - g.drawLine(nx, ny - 15, wx, wy - 15); - g.drawLine(wx, wy - 20, wx, wy - 10); - // g.setPaintMode(); - String displayString = - NumberFormat.getInstance() - .format( - isometricDistance(renderer, new ScreenPoint(nx, ny), new ScreenPoint(ex, ey))); - GraphicsUtil.drawBoxedString(g, displayString, nx + 25, ny - 25); - displayString = - NumberFormat.getInstance() - .format( - isometricDistance(renderer, new ScreenPoint(nx, ny), new ScreenPoint(wx, wy))); - GraphicsUtil.drawBoxedString(g, displayString, nx - 25, ny - 25); - } - } - public static void drawBoxedMeasurement( ZoneRenderer renderer, Graphics2D g, ScreenPoint startPoint, ScreenPoint endPoint) { if (!MapTool.getFrame().isPaintDrawingMeasurement()) { @@ -243,17 +174,9 @@ private static double euclideanDistance(ZoneRenderer renderer, ScreenPoint p1, S private static double isometricDistance(ZoneRenderer renderer, ScreenPoint p1, ScreenPoint p2) { double b = p2.y - p1.y; - // return b; return 2 * b * renderer.getZone().getUnitsPerCell() / renderer.getScaledGridSize(); } - protected static AffineTransform getPaintTransform(ZoneRenderer renderer) { - AffineTransform transform = new AffineTransform(); - transform.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); - transform.scale(renderer.getScale(), renderer.getScale()); - return transform; - } - protected static AbstractAction getDeleteTokenAction() { return deleteTokenAction; } diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java index afb6f30b69..04632be58f 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java @@ -14,11 +14,14 @@ */ package net.rptools.maptool.client.tool.drawing; +import java.awt.Graphics2D; import java.awt.event.MouseEvent; import net.rptools.maptool.client.ScreenPoint; import net.rptools.maptool.client.swing.SwingUtil; import net.rptools.maptool.client.tool.DefaultTool; +import net.rptools.maptool.client.tool.ToolHelper; import net.rptools.maptool.client.ui.zone.ZoneOverlay; +import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; import net.rptools.maptool.model.ZonePoint; public abstract class AbstractDrawingLikeTool extends DefaultTool implements ZoneOverlay { @@ -60,4 +63,39 @@ protected ZonePoint getPoint(MouseEvent e) { } return zp; } + + /** Draws the shape measurement as part of the overlay. */ + protected void drawMeasurementOverlay( + ZoneRenderer renderer, Graphics2D g, Measurement measurement) { + switch (measurement) { + case null -> {} + case Measurement.Rectangular rectangular -> { + var rectangle = rectangular.bounds(); + ToolHelper.drawBoxedMeasurement( + renderer, + g, + ScreenPoint.fromZonePoint(renderer, rectangle.getX(), rectangle.getY()), + ScreenPoint.fromZonePoint(renderer, rectangle.getMaxX(), rectangle.getMaxY())); + } + case Measurement.LineSegment lineSegment -> { + var p1 = + ScreenPoint.fromZonePoint(renderer, lineSegment.p1().getX(), lineSegment.p1().getY()); + var p2 = + ScreenPoint.fromZonePoint(renderer, lineSegment.p2().getX(), lineSegment.p2().getY()); + ToolHelper.drawMeasurement(renderer, g, p1, p2); + } + case Measurement.IsoRectangular isoRectangular -> { + var north = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.north().getX(), isoRectangular.north().getY()); + var west = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.west().getX(), isoRectangular.west().getY()); + var east = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.east().getX(), isoRectangular.east().getY()); + ToolHelper.drawIsoRectangleMeasurement(renderer, g, north, west, east); + } + } + } } diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java deleted file mode 100644 index 0826542b64..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.BasicStroke; -import java.awt.Cursor; -import java.awt.Graphics2D; -import java.awt.Polygon; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.event.MouseEvent; -import java.awt.geom.AffineTransform; -import java.awt.geom.Area; -import java.awt.geom.Path2D; -import java.util.List; -import net.rptools.maptool.client.AppStyle; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.MapToolUtil; -import net.rptools.maptool.client.swing.SwingUtil; -import net.rptools.maptool.client.swing.colorpicker.ColorPicker; -import net.rptools.maptool.client.ui.zone.ZoneOverlay; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.Token; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.model.Zone.Layer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** Tool for drawing freehand lines. */ -public abstract class AbstractDrawingTool extends AbstractDrawingLikeTool implements ZoneOverlay { - - private static final long serialVersionUID = 9121558405484986225L; - - private boolean isSnapToGridSelected; - private boolean isEraseSelected; - - protected Rectangle createRect(ZonePoint originPoint, ZonePoint newPoint) { - int x = Math.min(originPoint.x, newPoint.x); - int y = Math.min(originPoint.y, newPoint.y); - - int w = Math.max(originPoint.x, newPoint.x) - x; - int h = Math.max(originPoint.y, newPoint.y) - y; - - return new Rectangle(x, y, w, h); - } - - protected Shape createDiamond(ZonePoint originPoint, ZonePoint newPoint) { - int ox = originPoint.x; - int oy = originPoint.y; - int nx = newPoint.x; - int ny = newPoint.y; - int x1 = ox - (ny - oy) + ((nx - ox) / 2); - int y1 = ((oy + ny) / 2) - ((nx - ox) / 4); - int x2 = ox + (ny - oy) + ((nx - ox) / 2); - int y2 = ((oy + ny) / 2) + ((nx - ox) / 4); - int x[] = {originPoint.x, x1, nx, x2}; - int y[] = {originPoint.y, y1, ny, y2}; - return new Polygon(x, y, 4); - } - - protected Shape createHollowDiamond(ZonePoint originPoint, ZonePoint newPoint, Pen pen) { - int ox = originPoint.x; - int oy = originPoint.y; - int nx = newPoint.x; - int ny = newPoint.y; - int x1 = ox - (ny - oy) + ((nx - ox) / 2); - int y1 = ((oy + ny) / 2) - ((nx - ox) / 4); - int x2 = ox + (ny - oy) + ((nx - ox) / 2); - int y2 = ((oy + ny) / 2) + ((nx - ox) / 4); - int x[] = {originPoint.x, x1, nx, x2, originPoint.x}; - int y[] = {originPoint.y, y1, ny, y2, originPoint.y}; - - BasicStroke stroke = - new BasicStroke(pen.getThickness(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); - - Path2D path = new Path2D.Double(); - - for (int l = 0; l < 5; l++) { - if (path.getCurrentPoint() == null) { - path.moveTo(x[l], y[l]); - } else { - path.lineTo(x[l], y[l]); - } - } - - Area area = new Area(stroke.createStrokedShape(path)); - return area; - } - - protected AffineTransform getPaintTransform(ZoneRenderer renderer) { - AffineTransform transform = new AffineTransform(); - transform.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); - transform.scale(renderer.getScale(), renderer.getScale()); - return transform; - } - - protected void paintTransformed(Graphics2D g, ZoneRenderer renderer, Drawable drawing, Pen pen) { - AffineTransform transform = getPaintTransform(renderer); - AffineTransform oldTransform = g.getTransform(); - g.transform(transform); - drawing.draw(renderer.getZone(), g, pen); - g.setTransform(oldTransform); - } - - @Override - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - if (MapTool.getPlayer().isGM()) { - MapTool.getFrame() - .showControlPanel(MapTool.getFrame().getColorPicker(), getLayerSelectionDialog()); - } else { - MapTool.getFrame().showControlPanel(MapTool.getFrame().getColorPicker()); - } - renderer.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); - - MapTool.getFrame().getColorPicker().setSnapSelected(isSnapToGridSelected); - MapTool.getFrame().getColorPicker().setEraseSelected(isEraseSelected); - } - - @Override - protected void detachFrom(ZoneRenderer renderer) { - MapTool.getFrame().removeControlPanel(); - renderer.setCursor(Cursor.getDefaultCursor()); - - isSnapToGridSelected = MapTool.getFrame().getColorPicker().isSnapSelected(); - isEraseSelected = MapTool.getFrame().getColorPicker().isEraseSelected(); - - super.detachFrom(renderer); - } - - protected boolean isBackgroundFill(MouseEvent e) { - boolean defaultValue = MapTool.getFrame().getColorPicker().isFillBackgroundSelected(); - return defaultValue; - } - - protected boolean isEraser(MouseEvent e) { - boolean defaultValue = MapTool.getFrame().getColorPicker().isEraseSelected(); - if (SwingUtil.isShiftDown(e)) { - // Invert from the color panel - defaultValue = !defaultValue; - } - return defaultValue; - } - - protected boolean isSnapToGrid(MouseEvent e) { - boolean defaultValue = MapTool.getFrame().getColorPicker().isSnapSelected(); - if (SwingUtil.isControlDown(e)) { - // Invert from the color panel - defaultValue = !defaultValue; - } - return defaultValue; - } - - protected Pen getPen() { - Pen pen = new Pen(MapTool.getFrame().getPen()); - pen.setEraser(isEraser()); - - ColorPicker picker = MapTool.getFrame().getColorPicker(); - if (picker.isFillForegroundSelected()) { - pen.setForegroundMode(Pen.MODE_SOLID); - } else { - pen.setForegroundMode(Pen.MODE_TRANSPARENT); - } - if (picker.isFillBackgroundSelected()) { - pen.setBackgroundMode(Pen.MODE_SOLID); - } else { - pen.setBackgroundMode(Pen.MODE_TRANSPARENT); - } - pen.setSquareCap(picker.isSquareCapSelected()); - pen.setThickness(picker.getStrokeWidth()); - return pen; - } - - protected Area getTokenTopology(Zone.TopologyType topologyType) { - List topologyTokens = getZone().getTokensWithTopology(topologyType); - - Area tokenTopology = new Area(); - for (Token topologyToken : topologyTokens) { - tokenTopology.add(topologyToken.getTransformedTopology(topologyType)); - } - - return tokenTopology; - } - - @Override - public abstract void paintOverlay(ZoneRenderer renderer, Graphics2D g); - - protected void paintTopologyOverlay(Graphics2D g, Drawable drawable) { - paintTopologyOverlay(g, drawable, Pen.MODE_SOLID); - } - - protected void paintTopologyOverlay(Graphics2D g, Shape shape) { - ShapeDrawable drawable = null; - - if (shape != null) { - drawable = new ShapeDrawable(shape, false); - } - - paintTopologyOverlay(g, drawable, Pen.MODE_SOLID); - } - - protected void paintTopologyOverlay(Graphics2D g, Shape shape, int penMode) { - ShapeDrawable drawable = null; - - if (shape != null) { - drawable = new ShapeDrawable(shape, false); - } - - paintTopologyOverlay(g, drawable, penMode); - } - - protected void paintTopologyOverlay(Graphics2D g) { - Rectangle rectangle = null; - paintTopologyOverlay(g, rectangle, Pen.MODE_SOLID); - } - - protected void paintTopologyOverlay(Graphics2D g, Drawable drawable, int penMode) { - if (MapTool.getPlayer().isGM()) { - Zone zone = renderer.getZone(); - - Graphics2D g2 = (Graphics2D) g.create(); - g2.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); - g2.scale(renderer.getScale(), renderer.getScale()); - - g2.setColor(AppStyle.tokenMblColor); - g2.fill(getTokenTopology(Zone.TopologyType.MBL)); - g2.setColor(AppStyle.tokenTopologyColor); - g2.fill(getTokenTopology(Zone.TopologyType.WALL_VBL)); - g2.setColor(AppStyle.tokenHillVblColor); - g2.fill(getTokenTopology(Zone.TopologyType.HILL_VBL)); - g2.setColor(AppStyle.tokenPitVblColor); - g2.fill(getTokenTopology(Zone.TopologyType.PIT_VBL)); - g2.setColor(AppStyle.tokenCoverVblColor); - g2.fill(getTokenTopology(Zone.TopologyType.COVER_VBL)); - - g2.setColor(AppStyle.topologyTerrainColor); - g2.fill(zone.getTopology(Zone.TopologyType.MBL)); - - g2.setColor(AppStyle.topologyColor); - g2.fill(zone.getTopology(Zone.TopologyType.WALL_VBL)); - - g2.setColor(AppStyle.hillVblColor); - g2.fill(zone.getTopology(Zone.TopologyType.HILL_VBL)); - - g2.setColor(AppStyle.pitVblColor); - g2.fill(zone.getTopology(Zone.TopologyType.PIT_VBL)); - - g2.setColor(AppStyle.coverVblColor); - g2.fill(zone.getTopology(Zone.TopologyType.COVER_VBL)); - - g2.dispose(); - } - - if (drawable != null) { - Pen pen = new Pen(); - pen.setEraser(getPen().isEraser()); - pen.setOpacity(AppStyle.topologyRemoveColor.getAlpha() / 255.0f); - pen.setBackgroundMode(penMode); - - if (penMode == Pen.MODE_TRANSPARENT) { - pen.setThickness(3.0f); - } - - if (pen.isEraser()) { - pen.setEraser(false); - } - if (isEraser()) { - pen.setBackgroundPaint(new DrawableColorPaint(AppStyle.topologyRemoveColor)); - } else { - pen.setBackgroundPaint(new DrawableColorPaint(AppStyle.topologyAddColor)); - } - paintTransformed(g, renderer, drawable, pen); - } - } - - /** - * Render a drawable on a zone. This method consolidates all of the calls to the server in one - * place so that it is easier to keep them in sync. - * - * @param pen The pen used to draw. - * @param drawable What is being drawn. - */ - protected void completeDrawable(Pen pen, Drawable drawable) { - var zone = getZone(); - - if (!hasPaint(pen)) { - return; - } - if (drawable.getBounds(zone) == null) { - return; - } - if (MapTool.getPlayer().isGM()) { - drawable.setLayer(getSelectedLayer()); - } else { - drawable.setLayer(Layer.getDefaultPlayerLayer()); - } - - // Send new textures - MapToolUtil.uploadTexture(pen.getPaint()); - MapToolUtil.uploadTexture(pen.getBackgroundPaint()); - - // Tell the local/server to render the drawable. - MapTool.serverCommand().draw(zone.getId(), pen, drawable); - - // Allow it to be undone - zone.addDrawable(pen, drawable); - } - - private boolean hasPaint(Pen pen) { - return pen.getForegroundMode() != Pen.MODE_TRANSPARENT - || pen.getBackgroundMode() != Pen.MODE_TRANSPARENT; - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractTemplateTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractTemplateTool.java new file mode 100644 index 0000000000..f794433fad --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractTemplateTool.java @@ -0,0 +1,150 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Cursor; +import java.awt.Graphics2D; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.MapToolUtil; +import net.rptools.maptool.client.swing.SwingUtil; +import net.rptools.maptool.client.swing.colorpicker.ColorPicker; +import net.rptools.maptool.client.ui.zone.ZoneOverlay; +import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; +import net.rptools.maptool.model.Zone.Layer; +import net.rptools.maptool.model.drawing.Drawable; +import net.rptools.maptool.model.drawing.Pen; + +/** Base class for tools that draw templates. */ +public abstract class AbstractTemplateTool extends AbstractDrawingLikeTool implements ZoneOverlay { + + private static final long serialVersionUID = 9121558405484986225L; + + private boolean isSnapToGridSelected; + private boolean isEraseSelected; + + protected AffineTransform getPaintTransform(ZoneRenderer renderer) { + AffineTransform transform = new AffineTransform(); + transform.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); + transform.scale(renderer.getScale(), renderer.getScale()); + return transform; + } + + @Override + protected void attachTo(ZoneRenderer renderer) { + super.attachTo(renderer); + if (MapTool.getPlayer().isGM()) { + MapTool.getFrame() + .showControlPanel(MapTool.getFrame().getColorPicker(), getLayerSelectionDialog()); + } else { + MapTool.getFrame().showControlPanel(MapTool.getFrame().getColorPicker()); + } + renderer.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); + + MapTool.getFrame().getColorPicker().setSnapSelected(isSnapToGridSelected); + MapTool.getFrame().getColorPicker().setEraseSelected(isEraseSelected); + } + + @Override + protected void detachFrom(ZoneRenderer renderer) { + MapTool.getFrame().removeControlPanel(); + renderer.setCursor(Cursor.getDefaultCursor()); + + isSnapToGridSelected = MapTool.getFrame().getColorPicker().isSnapSelected(); + isEraseSelected = MapTool.getFrame().getColorPicker().isEraseSelected(); + + super.detachFrom(renderer); + } + + protected boolean isEraser(MouseEvent e) { + boolean defaultValue = MapTool.getFrame().getColorPicker().isEraseSelected(); + if (SwingUtil.isShiftDown(e)) { + // Invert from the color panel + defaultValue = !defaultValue; + } + return defaultValue; + } + + protected boolean isSnapToGrid(MouseEvent e) { + boolean defaultValue = MapTool.getFrame().getColorPicker().isSnapSelected(); + if (SwingUtil.isControlDown(e)) { + // Invert from the color panel + defaultValue = !defaultValue; + } + return defaultValue; + } + + protected Pen getPen() { + Pen pen = new Pen(MapTool.getFrame().getPen()); + pen.setEraser(isEraser()); + + ColorPicker picker = MapTool.getFrame().getColorPicker(); + if (picker.isFillForegroundSelected()) { + pen.setForegroundMode(Pen.MODE_SOLID); + } else { + pen.setForegroundMode(Pen.MODE_TRANSPARENT); + } + if (picker.isFillBackgroundSelected()) { + pen.setBackgroundMode(Pen.MODE_SOLID); + } else { + pen.setBackgroundMode(Pen.MODE_TRANSPARENT); + } + pen.setSquareCap(picker.isSquareCapSelected()); + pen.setThickness(picker.getStrokeWidth()); + return pen; + } + + @Override + public abstract void paintOverlay(ZoneRenderer renderer, Graphics2D g); + + /** + * Render a drawable on a zone. This method consolidates all of the calls to the server in one + * place so that it is easier to keep them in sync. + * + * @param pen The pen used to draw. + * @param drawable What is being drawn. + */ + protected void completeDrawable(Pen pen, Drawable drawable) { + var zone = getZone(); + + if (!hasPaint(pen)) { + return; + } + if (drawable.getBounds(zone) == null) { + return; + } + if (MapTool.getPlayer().isGM()) { + drawable.setLayer(getSelectedLayer()); + } else { + drawable.setLayer(Layer.getDefaultPlayerLayer()); + } + + // Send new textures + MapToolUtil.uploadTexture(pen.getPaint()); + MapToolUtil.uploadTexture(pen.getBackgroundPaint()); + + // Tell the local/server to render the drawable. + MapTool.serverCommand().draw(zone.getId(), pen, drawable); + + // Allow it to be undone + zone.addDrawable(pen, drawable); + } + + private boolean hasPaint(Pen pen) { + return pen.getForegroundMode() != Pen.MODE_TRANSPARENT + || pen.getBackgroundMode() != Pen.MODE_TRANSPARENT; + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java index 9232325049..2a74a42dd2 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java @@ -25,9 +25,7 @@ import javax.swing.SwingUtilities; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.MapToolUtil; -import net.rptools.maptool.client.ScreenPoint; import net.rptools.maptool.client.swing.colorpicker.ColorPicker; -import net.rptools.maptool.client.tool.ToolHelper; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; import net.rptools.maptool.model.Zone; import net.rptools.maptool.model.ZonePoint; @@ -226,39 +224,7 @@ public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { drawable.draw(renderer.getZone(), g2, pen); // Measurements - var measurement = result.measurement(); - switch (measurement) { - case null -> {} - case Measurement.Rectangular rectangular -> { - var rectangle = rectangular.bounds(); - ToolHelper.drawBoxedMeasurement( - renderer, - g, - ScreenPoint.fromZonePoint(renderer, rectangle.getX(), rectangle.getY()), - ScreenPoint.fromZonePoint(renderer, rectangle.getMaxX(), rectangle.getMaxY())); - } - case Measurement.LineSegment lineSegment -> { - var p1 = - ScreenPoint.fromZonePoint( - renderer, lineSegment.p1().getX(), lineSegment.p1().getY()); - var p2 = - ScreenPoint.fromZonePoint( - renderer, lineSegment.p2().getX(), lineSegment.p2().getY()); - ToolHelper.drawMeasurement(renderer, g, p1, p2); - } - case Measurement.IsoRectangular isoRectangular -> { - var north = - ScreenPoint.fromZonePoint( - renderer, isoRectangular.north().getX(), isoRectangular.north().getY()); - var west = - ScreenPoint.fromZonePoint( - renderer, isoRectangular.west().getX(), isoRectangular.west().getY()); - var east = - ScreenPoint.fromZonePoint( - renderer, isoRectangular.east().getX(), isoRectangular.east().getY()); - ToolHelper.drawIsoRectangleMeasurement(renderer, g, north, west, east); - } - } + drawMeasurementOverlay(renderer, g, result.measurement()); } } diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java index c74c30643f..5d3b1876cc 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java @@ -24,8 +24,6 @@ import javax.annotation.Nullable; import javax.swing.SwingUtilities; import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ScreenPoint; -import net.rptools.maptool.client.tool.ToolHelper; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; import net.rptools.maptool.model.GUID; import net.rptools.maptool.model.Zone; @@ -130,39 +128,7 @@ public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { g2.draw(result.shape()); // Measurements - var measurement = result.measurement(); - switch (measurement) { - case null -> {} - case Measurement.Rectangular rectangular -> { - var rectangle = rectangular.bounds(); - ToolHelper.drawBoxedMeasurement( - renderer, - g, - ScreenPoint.fromZonePoint(renderer, rectangle.getX(), rectangle.getY()), - ScreenPoint.fromZonePoint(renderer, rectangle.getMaxX(), rectangle.getMaxY())); - } - case Measurement.LineSegment lineSegment -> { - var p1 = - ScreenPoint.fromZonePoint( - renderer, lineSegment.p1().getX(), lineSegment.p1().getY()); - var p2 = - ScreenPoint.fromZonePoint( - renderer, lineSegment.p2().getX(), lineSegment.p2().getY()); - ToolHelper.drawMeasurement(renderer, g, p1, p2); - } - case Measurement.IsoRectangular isoRectangular -> { - var north = - ScreenPoint.fromZonePoint( - renderer, isoRectangular.north().getX(), isoRectangular.north().getY()); - var west = - ScreenPoint.fromZonePoint( - renderer, isoRectangular.west().getX(), isoRectangular.west().getY()); - var east = - ScreenPoint.fromZonePoint( - renderer, isoRectangular.east().getX(), isoRectangular.east().getY()); - ToolHelper.drawIsoRectangleMeasurement(renderer, g, north, west, east); - } - } + drawMeasurementOverlay(renderer, g, result.measurement()); } } diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusCellTemplateTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusCellTemplateTool.java index 1a0036ef85..3c58104802 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusCellTemplateTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusCellTemplateTool.java @@ -42,7 +42,7 @@ * * @author naciron */ -public class RadiusCellTemplateTool extends AbstractDrawingTool implements MouseMotionListener { +public class RadiusCellTemplateTool extends AbstractTemplateTool implements MouseMotionListener { /*--------------------------------------------------------------------------------------------- * Instance Variables *-------------------------------------------------------------------------------------------*/ @@ -337,7 +337,7 @@ protected void resetTool() { /** * It is OK to modify the pen returned by this method * - * @see net.rptools.maptool.client.tool.drawing.AbstractDrawingTool#getPen() + * @see AbstractTemplateTool#getPen() */ @Override protected Pen getPen() { diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusTemplateTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusTemplateTool.java index a5c84ff856..9683bee0b3 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusTemplateTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusTemplateTool.java @@ -44,7 +44,7 @@ * @version $Revision: 5945 $ $Date: 2013-06-03 04:35:50 +0930 (Mon, 03 Jun 2013) $ $Author: * azhrei_fje $ */ -public class RadiusTemplateTool extends AbstractDrawingTool implements MouseMotionListener { +public class RadiusTemplateTool extends AbstractTemplateTool implements MouseMotionListener { /*--------------------------------------------------------------------------------------------- * Instance Variables *-------------------------------------------------------------------------------------------*/ @@ -340,7 +340,7 @@ protected void resetTool() { /** * It is OK to modify the pen returned by this method * - * @see net.rptools.maptool.client.tool.drawing.AbstractDrawingTool#getPen() + * @see AbstractTemplateTool#getPen() */ @Override protected Pen getPen() { From b5362ebcf4e1d671ace7a5cdf1fa8c0df99b3b30 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Fri, 1 Nov 2024 10:31:07 -0700 Subject: [PATCH 146/146] Fix erase flag when DungeonDraftImporter adds topology to the zone --- .../client/utilities/DungeonDraftImporter.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/utilities/DungeonDraftImporter.java b/src/main/java/net/rptools/maptool/client/utilities/DungeonDraftImporter.java index 6dbf648826..761efb6fc9 100644 --- a/src/main/java/net/rptools/maptool/client/utilities/DungeonDraftImporter.java +++ b/src/main/java/net/rptools/maptool/client/utilities/DungeonDraftImporter.java @@ -203,8 +203,8 @@ public void importVTT() throws IOException { WALL_VBL_STROKE.createStrokedShape( getVBLPath(v.getAsJsonArray(), pixelsPerCell))); if (finalDo_transform) vblArea.transform(at); - zone.updateTopology(vblArea, true, Zone.TopologyType.WALL_VBL); - zone.updateTopology(vblArea, true, Zone.TopologyType.MBL); + zone.updateTopology(vblArea, false, Zone.TopologyType.WALL_VBL); + zone.updateTopology(vblArea, false, Zone.TopologyType.MBL); }); } @@ -215,8 +215,8 @@ public void importVTT() throws IOException { v -> { Area vblArea = new Area(getVBLPath(v.getAsJsonArray(), pixelsPerCell)); if (finalDo_transform) vblArea.transform(at); - zone.updateTopology(vblArea, true, Zone.TopologyType.HILL_VBL); - zone.updateTopology(vblArea, true, Zone.TopologyType.PIT_VBL); + zone.updateTopology(vblArea, false, Zone.TopologyType.HILL_VBL); + zone.updateTopology(vblArea, false, Zone.TopologyType.PIT_VBL); }); } @@ -239,8 +239,8 @@ public void importVTT() throws IOException { Area vblArea = new Area(DOOR_VBL_STROKE.createStrokedShape(getVBLPath(bounds, pixelsPerCell))); if (finalDo_transform) vblArea.transform(at); - zone.updateTopology(vblArea, true, Zone.TopologyType.WALL_VBL); - zone.updateTopology(vblArea, true, Zone.TopologyType.MBL); + zone.updateTopology(vblArea, false, Zone.TopologyType.WALL_VBL); + zone.updateTopology(vblArea, false, Zone.TopologyType.MBL); } }); }