From f1ee5b430e510253a16fb60c2df27e6c2f6a7316 Mon Sep 17 00:00:00 2001 From: bubblobill Date: Tue, 14 Jul 2026 10:56:43 +0800 Subject: [PATCH 01/12] Permissions VariableType Tables Editor DTO CampaignProperties --- build.gradle | 3 +- .../main/proto/data_transfer_objects.proto | 9 +- .../swing/MultiLineTableHeaderRenderer.java | 38 ++ .../TextFieldEditorButtonTableCellEditor.java | 1 + .../TokenPropertiesManagementPanel.java | 56 +- .../TokenPropertiesManagementPanelView.form | 135 ++++- .../TokenPropertiesTableModel.java | 71 ++- .../ui/sheet/stats/StatSheetListener.java | 15 +- .../ui/token/dialog/edit/EditTokenDialog.java | 290 +---------- .../edit/TokenPropertiesEditorPanel.java | 367 +++++++++++++ .../maptool/model/CampaignProperties.java | 68 +-- .../rptools/maptool/model/TokenProperty.java | 490 ++++++++++++------ .../rptools/maptool/model/VariableType.java | 33 ++ .../model/sheet/stats/StatSheetContext.java | 78 ++- .../client/ui/themes/AahLAF.properties | 45 +- .../rptools/maptool/language/i18n.properties | 44 +- .../maptool/language/i18n_cs_CZ.properties | 2 +- .../maptool/language/i18n_da_DK.properties | 2 +- .../maptool/language/i18n_de_DE.properties | 2 +- .../maptool/language/i18n_en_AU.properties | 26 +- .../maptool/language/i18n_en_GB.properties | 2 +- .../maptool/language/i18n_es_ES.properties | 2 +- .../maptool/language/i18n_fr_FR.properties | 2 +- .../maptool/language/i18n_it_IT.properties | 2 +- .../maptool/language/i18n_ja_JP.properties | 2 +- .../maptool/language/i18n_nl_NL.properties | 2 +- .../maptool/language/i18n_pl_PL.properties | 2 +- .../maptool/language/i18n_pt_BR.properties | 2 +- .../maptool/language/i18n_ru_RU.properties | 2 +- .../maptool/language/i18n_si_LK.properties | 2 +- .../maptool/language/i18n_sv_SE.properties | 2 +- .../maptool/language/i18n_uk_UA.properties | 2 +- .../maptool/language/i18n_zh_CN.properties | 2 +- .../maptool/model/TokenPropertiesTest.java | 18 +- 34 files changed, 1180 insertions(+), 639 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/client/swing/MultiLineTableHeaderRenderer.java create mode 100644 src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/TokenPropertiesEditorPanel.java create mode 100644 src/main/java/net/rptools/maptool/model/VariableType.java diff --git a/build.gradle b/build.gradle index 8a26fb2879..b9a3a87efb 100644 --- a/build.gradle +++ b/build.gradle @@ -91,7 +91,6 @@ ext { // vendor, tagVersion, appSemVer, and DSNs defaults are set in gradle.properties println "OS Detected: " + osdetector.os } - def modulesToOpen = [ 'java.desktop/java.awt', 'java.desktop/java.awt.geom', @@ -105,7 +104,9 @@ def modulesToOpen = [ 'java.desktop/sun.java2d', 'java.desktop/javax.swing', 'java.desktop/sun.awt.shell', + 'java.desktop/com.sun.java.swing.plaf.windows', ] + def jarManifestAttributes = [ 'Implementation-Title': project.name + developerRelease, 'Implementation-Version': tagVersion, diff --git a/messages/src/main/proto/data_transfer_objects.proto b/messages/src/main/proto/data_transfer_objects.proto index b2d8bcc0d1..2f8218432d 100644 --- a/messages/src/main/proto/data_transfer_objects.proto +++ b/messages/src/main/proto/data_transfer_objects.proto @@ -205,9 +205,12 @@ message SightTypeDto { message TokenPropertyDto { string name = 1; google.protobuf.StringValue short_name = 2; - bool high_priority = 3; - bool owner_only = 4; - bool gm_only = 5; + optional bool high_priority = 3; + optional bool owner_only = 4; + optional bool gm_only = 5; + optional string permissions = 8; + optional bool player_editable = 9; + optional string variable_type = 10; google.protobuf.StringValue default_value = 6; google.protobuf.StringValue display_name = 7; } diff --git a/src/main/java/net/rptools/maptool/client/swing/MultiLineTableHeaderRenderer.java b/src/main/java/net/rptools/maptool/client/swing/MultiLineTableHeaderRenderer.java new file mode 100644 index 0000000000..b69794da24 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/swing/MultiLineTableHeaderRenderer.java @@ -0,0 +1,38 @@ +package net.rptools.maptool.client.swing; + +import javax.swing.*; +import javax.swing.table.TableCellRenderer; +import java.awt.*; + +public class MultiLineTableHeaderRenderer implements TableCellRenderer { + private final Color fg, bg; + + public MultiLineTableHeaderRenderer() { + bg = UIManager.getDefaults().getColor("TableHeader.background"); + fg = UIManager.getDefaults().getColor("TableHeader.foreground"); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + JPanel panel = new JPanel(); + panel.setBackground(bg); + panel.setForeground(fg); + LookAndFeel.installBorder(panel, "TableHeader.cellBorder"); + + BoxLayout box = new BoxLayout(panel, BoxLayout.PAGE_AXIS); + panel.setLayout(box); + + String[] heading = ((String)value).split(" "); + for(String word: heading){ + JLabel label = new JLabel(word, null, SwingConstants.CENTER); + label.setBackground(bg); + label.setForeground(fg); + label.setOpaque(false); + label.setAlignmentX(0.5f); + panel.add(label); + } + panel.invalidate(); + + return panel; + } +} \ No newline at end of file diff --git a/src/main/java/net/rptools/maptool/client/swing/TextFieldEditorButtonTableCellEditor.java b/src/main/java/net/rptools/maptool/client/swing/TextFieldEditorButtonTableCellEditor.java index 67a85cc9fc..d344f37c03 100644 --- a/src/main/java/net/rptools/maptool/client/swing/TextFieldEditorButtonTableCellEditor.java +++ b/src/main/java/net/rptools/maptool/client/swing/TextFieldEditorButtonTableCellEditor.java @@ -40,6 +40,7 @@ public TextFieldEditorButtonTableCellEditor() { textField.addActionListener(l -> fireEditingStopped()); panel.add(textField); JButton button = new JButton("..."); + button.addActionListener( l -> MacroEditorDialog.createModalDialog( 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 ff41626f4f..c4a755d021 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 @@ -24,19 +24,21 @@ import java.util.stream.Stream; import javax.swing.*; import javax.swing.table.JTableHeader; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; + import net.rptools.CaseInsensitiveHashMap; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.swing.AbeillePanel; -import net.rptools.maptool.client.swing.TableCellRendererDecorator; +import net.rptools.maptool.client.swing.MultiLineTableHeaderRenderer; import net.rptools.maptool.client.swing.TextFieldEditorButtonTableCellEditor; import net.rptools.maptool.client.ui.campaignproperties.TokenPropertiesTableModel.LargeEditableText; import net.rptools.maptool.client.ui.sheet.stats.StatSheetComboBoxRenderer; import net.rptools.maptool.client.ui.theme.Icons; import net.rptools.maptool.client.ui.theme.RessourceManager; import net.rptools.maptool.language.I18N; -import net.rptools.maptool.model.Campaign; -import net.rptools.maptool.model.CampaignProperties; -import net.rptools.maptool.model.TokenProperty; +import net.rptools.maptool.model.*; import net.rptools.maptool.model.sheet.stats.StatSheet; import net.rptools.maptool.model.sheet.stats.StatSheetLocation; import net.rptools.maptool.model.sheet.stats.StatSheetManager; @@ -72,7 +74,6 @@ public void copyCampaignToUI(CampaignProperties cp) { .forEach( (k, v) -> tokenTypeMap.put(k, new ArrayList<>(v.stream().map(TokenProperty::new).toList()))); - var ssManager = new StatSheetManager(); tokenTypeMap .keySet() .forEach( @@ -372,9 +373,19 @@ public void initHelpButton() { public void initPropertyTable() { var propertyTable = getTokenPropertiesTable(); + propertyTable.setRowHeight(propertyTable.getRowHeight() + 8); propertyTable.setModel(new TokenPropertiesTableModel()); + propertyTable.setDefaultEditor( LargeEditableText.class, new TextFieldEditorButtonTableCellEditor()); + + propertyTable.setDefaultEditor( + VariableType.class, new DefaultCellEditor(new JComboBox<>(VariableType.values()))); + + propertyTable.setDefaultEditor( + PermissionsScope.class, new DefaultCellEditor(new JComboBox<>(PermissionsScope.values()))); + + propertyTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); propertyTable .getSelectionModel() .addListSelectionListener( @@ -461,11 +472,7 @@ public void initTypeList() { getTypeDuplicateButton().setEnabled(true); getTokenTypeName().setEditable(true); // Can't delete the default property - if (propertyType.equals(defaultPropertyType)) { - getTypeDeleteButton().setEnabled(false); - } else { - getTypeDeleteButton().setEnabled(true); - } + getTypeDeleteButton().setEnabled(!propertyType.equals(defaultPropertyType)); getStatSheetComboBox().setEnabled(true); populateStatSheetComboBoxes(propertyType); if (!propertyType.equals(defaultPropertyType)) { @@ -546,7 +553,6 @@ private void bind(String type) { } private void reset() { - bind((String) null); } @@ -615,17 +621,17 @@ private List parseTokenProperties(String propertyText) // Prefix while (true) { if (line.startsWith("*")) { - property.setShowOnStatSheet(true); + property.setVisibilityPermission(PermissionsScope.ALL); line = line.substring(1); continue; } if (line.startsWith("@")) { - property.setOwnerOnly(true); + property.setVisibilityPermission(PermissionsScope.OWNER); line = line.substring(1); continue; } if (line.startsWith("#")) { - property.setGMOnly(true); + property.setVisibilityPermission(PermissionsScope.GM); line = line.substring(1); continue; } @@ -641,7 +647,7 @@ private List parseTokenProperties(String propertyText) int indexDefault = line.indexOf(':'); if (indexDefault > 0) { String defaultVal = line.substring(indexDefault + 1).trim(); - if (defaultVal.length() > 0) { + if (!defaultVal.isEmpty()) { property.setDefaultValue(defaultVal); } @@ -659,7 +665,7 @@ private List parseTokenProperties(String propertyText) throw new IllegalArgumentException("Missing parenthesis"); } String shortName = line.substring(index + 1, indexClose).trim(); - if (shortName.length() > 0) { + if (!shortName.isEmpty()) { property.setShortName(shortName); } line = line.substring(0, index); @@ -720,7 +726,7 @@ public void prettify() { propertyTable.getTableHeader().setResizingAllowed(true); // Custom header that uses the column model to decide tooltips. - var header = + JTableHeader header = new JTableHeader(propertyTable.getColumnModel()) { @Override public String getToolTipText(MouseEvent event) { @@ -728,22 +734,22 @@ public String getToolTipText(MouseEvent event) { return model.getColumnTooltipText(columnIndex); } }; + propertyTable.setTableHeader(header); // The custom renderer delegates to the default one. - var customHeaderRenderer = new TableCellRendererDecorator(header.getDefaultRenderer()); - customHeaderRenderer.setHorizontalAlignment(SwingConstants.CENTER); - customHeaderRenderer.setVerticalAlignment(SwingConstants.CENTER); + TableCellRenderer customHeaderRenderer = new MultiLineTableHeaderRenderer(); for (int i = 0; i < propertyTable.getColumnCount(); i++) { - var column = propertyTable.getColumnModel().getColumn(i); - - column.setHeaderRenderer(customHeaderRenderer); + TableColumn column = propertyTable.getColumnModel().getColumn(i); + column.setHeaderRenderer(customHeaderRenderer); } + + propertyTable.doLayout(); } - private class TypeListModel extends AbstractListModel { - public Object getElementAt(int index) { + private class TypeListModel extends AbstractListModel { + public String getElementAt(int index) { List names = new ArrayList(tokenTypeMap.keySet()); Collections.sort(names); return names.get(index); diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanelView.form b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanelView.form index e5b1bff2fd..cba98c83da 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanelView.form +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanelView.form @@ -23,7 +23,7 @@ - + @@ -97,7 +97,7 @@ - + @@ -118,6 +118,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -129,86 +153,153 @@ - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + + + + + + + + + + + + - + - + - + + + + + + + + + + + + - + + + + + + + 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 3b7d6f1ff8..5676d03048 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 @@ -21,7 +21,9 @@ import java.util.Map; import javax.swing.table.AbstractTableModel; import net.rptools.maptool.language.I18N; +import net.rptools.maptool.model.PermissionsScope; import net.rptools.maptool.model.TokenProperty; +import net.rptools.maptool.model.VariableType; /** Table model for the token properties type table. */ public class TokenPropertiesTableModel extends AbstractTableModel { @@ -41,7 +43,7 @@ public record LargeEditableText(String text) {} */ private Map> tokenTypeMap = new HashMap<>(); - /** The token type that is currently displayed in the table. */ + /** The token property type on display in the table. */ private String tokenType = ""; /** @@ -56,7 +58,7 @@ public void setPropertyType(String propertyType) { @Override public int getRowCount() { - var properties = tokenTypeMap.get(tokenType); + List properties = tokenTypeMap.get(tokenType); return properties == null ? 0 : properties.size(); } @@ -67,19 +69,16 @@ public int getColumnCount() { @Override public Object getValueAt(int rowIndex, int columnIndex) { - var properties = tokenTypeMap.get(tokenType); - var property = properties.get(rowIndex); + List properties = tokenTypeMap.get(tokenType); + TokenProperty property = properties.get(rowIndex); return switch (columnIndex) { case 0 -> property.getName(); case 1 -> property.getShortName(); - case 2 -> { - var displayName = property.getDisplayName(); - yield displayName == null || displayName.isBlank() ? null : displayName; - } + case 2 -> property.getDisplayName(); case 3 -> property.getDefaultValue(); - case 4 -> property.isShowOnStatSheet(); - case 5 -> property.isGMOnly() & property.isShowOnStatSheet(); - case 6 -> property.isOwnerOnly() & property.isShowOnStatSheet(); + case 4 -> property.isPlayerEditable(); + case 5 -> property.getVariableType(); + case 6 -> property.getVisibilityPermission(); default -> null; }; } @@ -90,9 +89,9 @@ public String getColumnTooltipText(int column) { case 1 -> I18N.getText("campaignPropertiesTable.column.shortName.description"); case 2 -> I18N.getText("campaignPropertiesTable.column.displayName.description"); case 3 -> I18N.getText("campaignPropertiesTable.column.default.description"); - case 4 -> I18N.getText("campaignPropertiesTable.column.statSheet.description"); - case 5 -> I18N.getText("campaignPropertiesTable.column.gm.description"); - case 6 -> I18N.getText("campaignPropertiesTable.column.owner.description"); + case 4 -> I18N.getText("campaignPropertiesTable.column.statSheet.playerEditable"); + case 5 -> I18N.getText("campaignPropertiesTable.column.valueType.description"); + case 6 -> I18N.getText("campaignPropertiesTable.column.statSheet.description"); default -> ""; }; } @@ -104,9 +103,9 @@ public String getColumnName(int column) { case 1 -> I18N.getText("campaignPropertiesTable.column.shortName"); case 2 -> I18N.getText("campaignPropertiesTable.column.displayName"); case 3 -> I18N.getText("campaignPropertiesTable.column.defaultValue"); - case 4 -> I18N.getText("campaignPropertiesTable.column.onStatSheet"); - case 5 -> I18N.getText("campaignPropertiesTable.column.gmStatSheet"); - case 6 -> I18N.getText("campaignPropertiesTable.column.ownerStatSheet"); + case 4 -> I18N.getText("campaignPropertiesTable.column.playerEditable"); + case 5 -> I18N.getText("campaignPropertiesTable.column.valueType"); + case 6 -> I18N.getText("campaignPropertiesTable.column.statSheetVisibility"); default -> ""; }; } @@ -116,43 +115,41 @@ public Class getColumnClass(int columnIndex) { return switch (columnIndex) { case 0, 1, 2 -> String.class; case 3 -> LargeEditableText.class; - case 4, 5, 6 -> Boolean.class; + case 4 -> Boolean.class; + case 5 -> VariableType.class; + case 6 -> PermissionsScope.class; default -> null; }; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { - var properties = tokenTypeMap.get(tokenType); - var tokenProperty = properties.get(rowIndex); - return switch (columnIndex) { - case 5, 6 -> - tokenProperty.isShowOnStatSheet(); // GM, Owner only editable if show on stat sheet is set - default -> true; - }; + if(columnIndex == 5) { + List properties = tokenTypeMap.get(tokenType); + return properties.get(rowIndex).isPlayerEditable(); + } + return true; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { - var properties = tokenTypeMap.get(tokenType); - var tokenProperty = properties.get(rowIndex); + List properties = tokenTypeMap.get(tokenType); + TokenProperty tokenProperty = properties.get(rowIndex); + switch (columnIndex) { case 0 -> tokenProperty.setName((String) aValue); case 1 -> tokenProperty.setShortName((String) aValue); case 2 -> tokenProperty.setDisplayName((String) aValue); case 3 -> tokenProperty.setDefaultValue((String) aValue); - case 4 -> { - tokenProperty.setShowOnStatSheet((Boolean) aValue); - fireTableRowsUpdated(rowIndex, rowIndex); - } - case 5 -> tokenProperty.setGMOnly((Boolean) aValue); - case 6 -> tokenProperty.setOwnerOnly((Boolean) aValue); + case 4 -> tokenProperty.setPlayerEditable((boolean) aValue); + case 5 -> tokenProperty.setVariableType((VariableType) aValue); + case 6 -> tokenProperty.setVisibilityPermission((PermissionsScope) aValue); } } /** Adds a new token property, with a generated name. */ public void addProperty(int selectedRow) { - var properties = tokenTypeMap.get(tokenType); + List properties = tokenTypeMap.get(tokenType); // First find a unique name, there are so few entries we don't have to worry // about being fancy @@ -189,13 +186,13 @@ public void addProperty(int selectedRow) { * @param selectedRow the selected row to delete. */ public void deleteProperty(int selectedRow) { - var properties = tokenTypeMap.get(tokenType); + List properties = tokenTypeMap.get(tokenType); properties.remove(selectedRow); fireTableRowsDeleted(selectedRow, selectedRow); } public void movePropertyUp(int selectedRow) { - var properties = tokenTypeMap.get(tokenType); + List properties = tokenTypeMap.get(tokenType); if (selectedRow <= 0 || selectedRow >= properties.size()) { // Either already at the top or a nonsense index. throw new ArrayIndexOutOfBoundsException(selectedRow); @@ -206,7 +203,7 @@ public void movePropertyUp(int selectedRow) { } public void movePropertyDown(int selectedRow) { - var properties = tokenTypeMap.get(tokenType); + List properties = tokenTypeMap.get(tokenType); if (selectedRow < 0 || selectedRow >= properties.size() - 1) { // Either already at the bottom or a nonsense index. throw new ArrayIndexOutOfBoundsException(selectedRow); 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 c39201e107..b42b7c1fcb 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 @@ -16,11 +16,9 @@ import com.google.common.eventbus.Subscribe; import net.rptools.maptool.client.AppPreferences; -import net.rptools.maptool.client.AppUtil; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.events.TokenHoverEnter; import net.rptools.maptool.client.events.TokenHoverExit; -import net.rptools.maptool.model.Token.Type; import net.rptools.maptool.model.sheet.stats.StatSheetManager; /** @@ -55,16 +53,9 @@ public void onHoverEnter(TokenHoverEnter event) { statSheet = new StatSheet(); var ssId = ssProperties.id(); var ssRecord = ssManager.getStatSheet(ssId); - var token = event.token(); - if (MapTool.getPlayer().isGM() - || AppUtil.playerOwns(token) - || token.getType() == Type.NPC) { - statSheet.setContent( - event, - ssManager.getStatSheetContent(ssId), - ssRecord.entry(), - ssProperties.location()); - } + + statSheet.setContent( + event, ssManager.getStatSheetContent(ssId), ssRecord.entry(), ssProperties.location()); } } } 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 4478170f9e..c25f4d7b0b 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 @@ -15,19 +15,10 @@ package net.rptools.maptool.client.ui.token.dialog.edit; import com.google.common.collect.Iterables; -import com.jidesoft.combobox.MultilineStringExComboBox; -import com.jidesoft.combobox.PopupPanel; -import com.jidesoft.grid.MultilineStringCellEditor; -import com.jidesoft.grid.NavigableModel; -import com.jidesoft.grid.Property; -import com.jidesoft.grid.PropertyPane; import com.jidesoft.grid.PropertyTable; -import com.jidesoft.grid.PropertyTableModel; -import com.jidesoft.plaf.basic.BasicExComboBoxUI; import com.jidesoft.swing.CheckBoxListWithSelectable; import com.jidesoft.swing.DefaultSelectable; import com.jidesoft.swing.Selectable; -import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; @@ -44,14 +35,12 @@ import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; -import javax.annotation.Nullable; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableCellRenderer; import javax.swing.text.Position.Bias; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.HTMLEditorKit; @@ -113,7 +102,6 @@ public class EditTokenDialog extends AbeillePanel { private final TokenPropertiesDialog view; private final RSyntaxTextArea xmlStatblockRSyntaxTextArea = new RSyntaxTextArea(2, 2); private final RSyntaxTextArea textStatblockRSyntaxTextArea = new RSyntaxTextArea(2, 2); - private final WordWrapCellRenderer propertyCellRenderer = new WordWrapCellRenderer(); private boolean tokenSaved; private final GenericDialogFactory dialogFactory = @@ -125,9 +113,12 @@ public class EditTokenDialog extends AbeillePanel { private AutoGenerateTopologySwingWorker autoGenerateTopologySwingWorker = new AutoGenerateTopologySwingWorker(false, Color.BLACK); + private TokenPropertiesEditorPanel tokenPropertiesEditorPanel; + private EditTokenDialog(TokenPropertiesDialog view) { super(view.getRootComponent()); this.view = view; + tokenPropertiesEditorPanel = new TokenPropertiesEditorPanel(this); panelInit(); } @@ -136,11 +127,6 @@ public EditTokenDialog() { this(new TokenPropertiesDialog()); } - @SuppressWarnings("unused") - public void initPropertyTable() { - getPropertyTable().setModel(new TokenPropertyTableModel()); - } - @SuppressWarnings("unused") public void initGMNotesEditorPane() { setGmNotesEnabled(MapTool.getPlayer().isGM()); @@ -374,7 +360,7 @@ public void bind(final Token token) { var propertyType = token.getPropertyType(); getPropertyTypeCombo().setSelectedItem(propertyType); /* Make sure the right properties are displayed. */ - updatePropertiesTable(token, propertyType); + tokenPropertiesEditorPanel.updatePropertiesTable(token, propertyType); getSightTypeCombo() .setSelectedItem( @@ -647,7 +633,7 @@ public void initPropertyTypeCombo() { .addItemListener( e -> { if (e.getStateChange() == ItemEvent.SELECTED) { - updatePropertiesTable( + tokenPropertiesEditorPanel.updatePropertiesTable( getModel(), (String) getPropertyTypeCombo().getSelectedItem()); } }); @@ -680,22 +666,6 @@ private void updateImageTableCombo() { getImageTableCombo().setModel(model); } - /** - * Updates the property table. - * - * @param propertyType the property type of the token (unused). - */ - private void updatePropertiesTable(@Nullable Token token, final String propertyType) { - EventQueue.invokeLater( - () -> { - PropertyTable pp = getPropertyTable(); - var propertyList = MapTool.getCampaign().getTokenPropertyList(propertyType); - pp.setModel( - new TokenPropertyTableModel(token, propertyType, propertyList, propertyCellRenderer)); - pp.expandAll(); - }); - } - public JComboBox getSizeCombo() { return (JComboBox) getComponent("size"); } @@ -917,12 +887,13 @@ public boolean commit() { /* Properties */ var tableModel = getPropertyTable().getModel(); - if (getPropertyTable().getModel() instanceof TokenPropertyTableModel tokenPropertyTableModel) { + if (getPropertyTable().getModel() + instanceof TokenPropertiesEditorPanel.TokenPropertyTableModel tokenPropertyTableModel) { tokenPropertyTableModel.applyTo(token); } else { log.warn( "Property table model is not of the expected type; expected {} but got {}", - TokenPropertyTableModel.class, + TokenPropertiesEditorPanel.TokenPropertyTableModel.class, tableModel.getClass()); } @@ -1251,31 +1222,8 @@ public void initOwnershipPanel() { } public void initPropertiesPanel() { - PropertyTable propertyTable = - new PropertyTable() { - @Override - public String getToolTipText(MouseEvent event) { - String text = super.getToolTipText(event); - return text != null && text.length() > 100 ? text.substring(0, 100) + " ..." : text; - } - }; - propertyTable.setFillsViewportHeight(true); - propertyTable.setName("propertiesTable"); - - /* wrap button and functionality */ - JPanel buttonsAndPropertyTable = new JPanel(); - buttonsAndPropertyTable.setLayout(new BorderLayout()); - JCheckBox wrapToggle = new JCheckBox(I18N.getString("EditTokenDialog.msg.wrap")); - wrapToggle.addActionListener( - e -> { - propertyCellRenderer.setLineWrap(wrapToggle.isSelected()); - propertyTable.repaint(); - }); - buttonsAndPropertyTable.add(wrapToggle, BorderLayout.PAGE_END); - - PropertyPane pane = new PropertyPane(propertyTable); - buttonsAndPropertyTable.add(pane, BorderLayout.CENTER); - replaceComponent("propertiesPanel", "propertiesTable", buttonsAndPropertyTable); + tokenPropertiesEditorPanel.reset(getModel()); + replaceComponent("propertiesPanel", "propertiesTable", tokenPropertiesEditorPanel); } public void initTokenLayoutPanel() { @@ -1918,143 +1866,6 @@ public Map getMap() { } } - /* needed to change the popup for properties */ - private static class MTMultilineStringExComboBox extends MultilineStringExComboBox { - - final ResourceBundle a = ResourceBundle.getBundle("com.jidesoft.combobox.combobox"); - - public ResourceBundle getResourceBundle(Locale paramLocale) { - return ResourceBundle.getBundle("com.jidesoft.combobox.combobox", paramLocale); - } - - public PopupPanel createPopupComponent() { - MTMultilineStringPopupPanel pp = - new MTMultilineStringPopupPanel( - getResourceBundle(Locale.getDefault()).getString("ComboBox.multilineStringTitle")); - return pp; - } - } - - /* the cell editor for property popups */ - private static class MTMultilineStringCellEditor extends MultilineStringCellEditor { - - protected MTMultilineStringExComboBox createMultilineStringComboBox() { - MTMultilineStringExComboBox localMultilineStringExComboBox = - new MTMultilineStringExComboBox(); - localMultilineStringExComboBox.setEditable(true); - localMultilineStringExComboBox.setUI(new BasicExComboBoxUI()); - return localMultilineStringExComboBox; - } - } - - /* the property popup table */ - private static class MTMultilineStringPopupPanel extends PopupPanel { - - private RSyntaxTextArea j = createTextArea(); - - public MTMultilineStringPopupPanel() { - this(""); - } - - public MTMultilineStringPopupPanel(String paramString) { - this.setResizable(true); - /* Set the color style via Theme */ - try { - File themeFile = - new File( - AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); - Theme theme = Theme.load(new FileInputStream(themeFile)); - theme.apply(j); - - j.revalidate(); - } catch (IOException e) { - log.error("Error while loading multiline property editor theme", e); - } - JScrollPane localJScrollPane = new RTextScrollPane(j); - localJScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); - localJScrollPane.setAutoscrolls(true); - localJScrollPane.setPreferredSize(new Dimension(300, 200)); - setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 5)); - setLayout(new BorderLayout()); - setTitle(paramString); - add(localJScrollPane, "Center"); - setDefaultFocusComponent(j); - j.setLineWrap(false); - JCheckBox wrapToggle = new JCheckBox(I18N.getString("EditTokenDialog.msg.wrap")); - wrapToggle.addActionListener(e -> j.setLineWrap(!j.getLineWrap())); - - DefaultComboBoxModel syntaxListModel = new DefaultComboBoxModel(); - syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_NONE); - syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_JSON); - syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE); - syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_HTML); - syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_XML); - JComboBox syntaxComboBox = new JComboBox(syntaxListModel); - syntaxComboBox.addActionListener( - e -> j.setSyntaxEditingStyle(syntaxComboBox.getSelectedItem().toString())); - - add(syntaxComboBox, BorderLayout.BEFORE_FIRST_LINE); - add(wrapToggle, BorderLayout.AFTER_LAST_LINE); - } - - public Object getSelectedObject() { - return j.getText(); - } - - public void setSelectedObject(Object paramObject) { - if (paramObject != null) { - j.setText(paramObject.toString()); - } else { - j.setText(""); - } - } - - protected RSyntaxTextArea createTextArea() { - RSyntaxTextArea textArea = new RSyntaxTextArea(); - textArea.setUseFocusableTips(false); - textArea.setAnimateBracketMatching(true); - textArea.setBracketMatchingEnabled(true); - textArea.setLineWrap(false); - textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE); - return textArea; - } - } - - /* cell renderer for properties table */ - private static class WordWrapCellRenderer extends RSyntaxTextArea implements TableCellRenderer { - - WordWrapCellRenderer() { - setLineWrap(false); - setWrapStyleWord(true); - - /* Set the color style via Theme */ - try { - File themeFile = - new File( - AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); - Theme theme = Theme.load(new FileInputStream(themeFile)); - theme.apply(this); - - revalidate(); - } catch (IOException e) { - log.error("Error while loading theme", e); - } - } - - public Component getTableCellRendererComponent( - JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - if (value == null) { - value = ""; - } - setText(value.toString()); - setSize(table.getColumnModel().getColumn(column).getWidth(), getPreferredSize().height); - if (table.getRowHeight(row) != getPreferredSize().height) { - table.setRowHeight(row, getPreferredSize().height); - } - return this; - } - } - private class AutoGenerateTopologySwingWorker extends SwingWorker { private final boolean regenerate; private final Color ignoredColor; @@ -2147,87 +1958,6 @@ public Component getListCellRendererComponent( } /* MODELS */ - private static class TokenPropertyTableModel - extends PropertyTableModel - implements NavigableModel { - private final List propertyList; - private final Map propertyMap; - - public TokenPropertyTableModel() { - propertyList = List.of(); - propertyMap = Map.of(); - } - - public TokenPropertyTableModel( - @Nullable Token model, - String propertyType, - List propertyList, - TableCellRenderer propertyCellRenderer) { - this.propertyList = propertyList; - - this.propertyMap = new HashMap<>(); - for (TokenProperty property : propertyList) { - String value = model == null ? null : (String) model.getProperty(property.getName()); - if (value == null) { - value = property.getDefaultValue(); - } - this.propertyMap.put(property.getName(), value); - } - - var gridProperties = new ArrayList(); - for (var tokenProperty : propertyList) { - var gridProperty = new EditTokenProperty(tokenProperty.getName(), propertyType); - gridProperty.setTableCellRenderer(propertyCellRenderer); - gridProperty.setCellEditor(new MTMultilineStringCellEditor()); - gridProperties.add(gridProperty); - } - setOriginalProperties(gridProperties); - } - - public void applyTo(Token token) { - for (TokenProperty property : propertyList) { - String value = propertyMap.get(property.getName()); - if (property.getDefaultValue() != null && property.getDefaultValue().equals(value)) { - token.setProperty(property.getName(), null); // Clear original value - continue; - } - token.setProperty(property.getName(), value); - } - } - - @Override - public boolean isNavigableAt(int rowIndex, int columnIndex) { - /* make the property name column non-navigable so that tab takes you directly to the next property value cell. */ - return (columnIndex != 0); - } - - @Override - public boolean isNavigationOn() { - return true; - } - - class EditTokenProperty extends Property { - public EditTokenProperty(String key, String propertyType) { - super(key, key, String.class, propertyType); - } - - @Override - public Object getValue() { - return propertyMap.get(getName()); - } - - @Override - public void setValue(Object value) { - propertyMap.put(getName(), (String) value); - } - - @Override - public boolean hasValue() { - return propertyMap.get(getName()) != null; - } - } - } - private static final class OwnerListModel extends AbstractListModel { private static final long serialVersionUID = 2375600545516097234L; diff --git a/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/TokenPropertiesEditorPanel.java b/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/TokenPropertiesEditorPanel.java new file mode 100644 index 0000000000..cec01c2211 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/TokenPropertiesEditorPanel.java @@ -0,0 +1,367 @@ +/* + * 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.token.dialog.edit; + +import com.jidesoft.combobox.MultilineStringExComboBox; +import com.jidesoft.combobox.PopupPanel; +import com.jidesoft.converter.ConverterContext; +import com.jidesoft.grid.*; +import com.jidesoft.plaf.basic.BasicExComboBoxUI; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.util.*; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import javax.swing.*; +import javax.swing.table.TableCellRenderer; + +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.language.I18N; +import net.rptools.maptool.model.Token; +import net.rptools.maptool.model.TokenProperty; +import net.rptools.maptool.model.VariableType; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.fife.ui.rtextarea.RTextScrollPane; + +import static com.jidesoft.converter.ConverterContext.DEFAULT_CONTEXT; + +public class TokenPropertiesEditorPanel extends PropertyPane { + private static final Logger log = LogManager.getLogger(); + + private final EditTokenDialog editTokenDialog; + private final WordWrapCellRenderer wordWrapCellRenderer = new WordWrapCellRenderer(); + private final NumberCellRenderer numberCellRenderer = new NumberCellRenderer(); + private final NumberCellEditor numberCellEditor = new NumberCellEditor<>(); + private Token token; + private PropertyTableSearchable searchable; + + public TokenPropertiesEditorPanel(EditTokenDialog editTokenDialog) { + super(new PropertyTable() { + @Override + public String getToolTipText(MouseEvent event) { + String text = super.getToolTipText(event); + return text != null && text.length() > 100 ? text.substring(0, 100) + " ..." : text; + } + }, 1); + + this.editTokenDialog = editTokenDialog; + + setShowDescription(false); + + searchable = new PropertyTableSearchable(getPropertyTable()); + + getPropertyTable().setModel(new TokenPropertyTableModel()); + getPropertyTable().setFillsViewportHeight(true); + getPropertyTable().setName("propertiesTable"); + + + /* wrap button and functionality */ + JPanel buttonsAndPropertyTable = new JPanel(); + buttonsAndPropertyTable.setLayout(new BorderLayout()); + JCheckBox wrapToggle = new JCheckBox(I18N.getString("EditTokenDialog.msg.wrap")); + wrapToggle.addActionListener( + e -> { + wordWrapCellRenderer.setLineWrap(wrapToggle.isSelected()); + getPropertyTable().repaint(); + }); + buttonsAndPropertyTable.add(wrapToggle, BorderLayout.PAGE_END); + + + buttonsAndPropertyTable.add(this, BorderLayout.CENTER); + setMinimumSize(new Dimension(300, 200)); + setPreferredSize(new Dimension(-1, -1)); + setVisible(true); + } + + public void reset(Token token) { + } + + + /** + * Updates the property table. + * + * @param propertyType the property type of the token (unused). + */ + protected void updatePropertiesTable(@Nullable Token token, final String propertyType) { + EventQueue.invokeLater( + () -> { + PropertyTable pp = getPropertyTable(); + List propertyTypeProperties = MapTool.getCampaign().getTokenPropertyList(propertyType); + pp.setModel( + new TokenPropertyTableModel(token, propertyType, propertyTypeProperties, wordWrapCellRenderer, numberCellEditor, numberCellRenderer)); + pp.expandAll(); + }); + } + + protected static class TokenPropertyTableModel + extends PropertyTableModel + implements NavigableModel { + final String gm = String.format(" (%s)", I18N.getText("permissionsScope.displayName.gm")); + private final java.util.List propertyList; + private final Map propertyMap; + + public TokenPropertyTableModel() { + propertyList = java.util.List.of(); + propertyMap = Map.of(); + } + + public TokenPropertyTableModel( + @Nullable Token model, + String propertyTypeName, + List propertyList, + WordWrapCellRenderer wordWrapCellRenderer, + NumberCellEditor numberCellEditor, + NumberCellRenderer numberCellRenderer) { + this.propertyList = propertyList; + + Set typePropertyNames = propertyList.stream().map(TokenProperty::getName).collect(Collectors.toSet()); + Set otherPropertyNames = Set.of(); + if (model != null) { + otherPropertyNames = model.getPropertyNamesRaw(); + otherPropertyNames = otherPropertyNames.stream().filter(name -> !typePropertyNames.stream().toList().contains(name)).collect(Collectors.toSet()); + } + + this.propertyMap = new HashMap<>(); + + ArrayList gridProperties = new ArrayList<>(); + for(TokenProperty tp : propertyList){ + String value = null; + if(model != null){ + value = (String) model.getProperty(tp.getName()); + } + this.propertyMap.put(tp.getName(), value == null && tp.hasDefaultValue() ? tp.getDefaultValue() : value); + + TableTokenProperty gridProperty = new TableTokenProperty(tp, propertyTypeName); + if(tp.getVariableType().equals(VariableType.NUMBER)){ + gridProperty.setTableCellRenderer(numberCellRenderer); + gridProperty.setCellEditor(numberCellEditor); + } else { + gridProperty.setTableCellRenderer(wordWrapCellRenderer); + gridProperty.setCellEditor(new MTMultilineStringCellEditor()); + } + gridProperties.add(gridProperty); + } + for (String propName : otherPropertyNames) { + this.propertyMap.put(propName, (String) model.getProperty(propName)); + + TableTokenProperty gridProperty = new TableTokenProperty(propName, propertyTypeName, gm); + gridProperty.setTableCellRenderer(wordWrapCellRenderer); + gridProperty.setCellEditor(new MTMultilineStringCellEditor()); + gridProperties.add(gridProperty); + } + + setOriginalProperties(gridProperties); + } + + public void applyTo(Token token) { + for (TokenProperty property : propertyList) { + String value = propertyMap.get(property.getName()); + if (property.getDefaultValue() != null && property.getDefaultValue().equals(value)) { + token.setProperty(property.getName(), null); // Clear original value + continue; + } + token.setProperty(property.getName(), value); + } + } + + @Override + public boolean isNavigableAt(int rowIndex, int columnIndex) { + /* make the property name column non-navigable so that tab takes you directly to the next property value cell. */ + return (columnIndex != 0); + } + + @Override + public boolean isNavigationOn() { + return true; + } + + class TableTokenProperty extends Property { + public TableTokenProperty(TokenProperty tokenProperty, String propertyType) { + this(tokenProperty.getName(), + tokenProperty.getDisplayName(), + tokenProperty.getVariableType() == null ? VariableType.UNDEFINED.getClass() : tokenProperty.getVariableType().getKlass(), + tokenProperty.isPlayerEditable() ? propertyType : propertyType + gm, + DEFAULT_CONTEXT, + null); + } + + public TableTokenProperty(String propertyName, String propertyType, String category) { + this(propertyName, propertyName, String.class, propertyType, DEFAULT_CONTEXT, null); + } + + public TableTokenProperty(String name, String displayName, Class klass, String category, ConverterContext converterContext, List children) { + super(name, "", klass, category, converterContext, children); + setDisplayName(displayName); + } + + @Override + public Object getValue() { + return propertyMap.get(getName()); + } + + @Override + public void setValue(Object value) { + propertyMap.put(getName(), (String) value); + } + + @Override + public boolean hasValue() { + return propertyMap.get(getName()) != null; + } + } + } + + /* needed to change the popup for properties */ + private static class MTMultilineStringExComboBox extends MultilineStringExComboBox { + + final ResourceBundle a = ResourceBundle.getBundle("com.jidesoft.combobox.combobox"); + + public ResourceBundle getResourceBundle(Locale paramLocale) { + return ResourceBundle.getBundle("com.jidesoft.combobox.combobox", paramLocale); + } + + public PopupPanel createPopupComponent() { + MTMultilineStringPopupPanel pp = + new MTMultilineStringPopupPanel( + getResourceBundle(Locale.getDefault()).getString("ComboBox.multilineStringTitle")); + return pp; + } + } + + /* the cell editor for property popups */ + private static class MTMultilineStringCellEditor extends MultilineStringCellEditor { + + protected MTMultilineStringExComboBox createMultilineStringComboBox() { + MTMultilineStringExComboBox localMultilineStringExComboBox = + new MTMultilineStringExComboBox(); + localMultilineStringExComboBox.setEditable(true); + localMultilineStringExComboBox.setUI(new BasicExComboBoxUI()); + return localMultilineStringExComboBox; + } + } + + /* cell renderer for properties table */ + protected static class WordWrapCellRenderer extends RSyntaxTextArea implements TableCellRenderer { + + WordWrapCellRenderer() { + setLineWrap(false); + setWrapStyleWord(true); +// +// /* Set the color style via Theme */ +// try { +// File themeFile = +// new File( +// AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); +// Theme theme = Theme.load(new FileInputStream(themeFile)); +// theme.apply(this); +// +// revalidate(); +// } catch (IOException e) { +// log.error("Error while loading theme", e); +// } + } + + public Component getTableCellRendererComponent( + JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + if (value == null) { + value = ""; + } + setText(value.toString()); + setSize(table.getColumnModel().getColumn(column).getWidth(), getPreferredSize().height); + if (table.getRowHeight(row) != getPreferredSize().height) { + table.setRowHeight(row, getPreferredSize().height); + } + return this; + } + } + + /* the property popup table */ + private static class MTMultilineStringPopupPanel extends PopupPanel { + + private RSyntaxTextArea j = createTextArea(); + + public MTMultilineStringPopupPanel() { + this(""); + } + + public MTMultilineStringPopupPanel(String paramString) { + this.setResizable(true); +// /* Set the color style via Theme */ +// try { +// File themeFile = +// new File( +// AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); +// Theme theme = Theme.load(new FileInputStream(themeFile)); +// theme.apply(j); +// +// j.revalidate(); +// } catch (IOException e) { +// log.error("Error while loading multiline property editor theme", e); +// } + JScrollPane localJScrollPane = new RTextScrollPane(j); + localJScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + localJScrollPane.setAutoscrolls(true); + localJScrollPane.setPreferredSize(new Dimension(300, 200)); + setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 5)); + setLayout(new BorderLayout()); + setTitle(paramString); + add(localJScrollPane, "Center"); + setDefaultFocusComponent(j); + j.setLineWrap(false); + JCheckBox wrapToggle = new JCheckBox(I18N.getString("EditTokenDialog.msg.wrap")); + wrapToggle.addActionListener(e -> j.setLineWrap(!j.getLineWrap())); + + DefaultComboBoxModel syntaxListModel = new DefaultComboBoxModel(); + syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_NONE); + syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_JSON); + syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE); + syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_HTML); + syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_XML); + JComboBox syntaxComboBox = new JComboBox(syntaxListModel); + syntaxComboBox.addActionListener( + e -> j.setSyntaxEditingStyle(syntaxComboBox.getSelectedItem().toString())); + + add(syntaxComboBox, BorderLayout.BEFORE_FIRST_LINE); + add(wrapToggle, BorderLayout.AFTER_LAST_LINE); + } + + public Object getSelectedObject() { + return j.getText(); + } + + public void setSelectedObject(Object paramObject) { + if (paramObject != null) { + j.setText(paramObject.toString()); + } else { + j.setText(""); + } + } + + protected RSyntaxTextArea createTextArea() { + RSyntaxTextArea textArea = new RSyntaxTextArea(); + textArea.setUseFocusableTips(false); + textArea.setAnimateBracketMatching(true); + textArea.setBracketMatchingEnabled(true); + textArea.setLineWrap(false); + textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE); + return textArea; + } + } +} diff --git a/src/main/java/net/rptools/maptool/model/CampaignProperties.java b/src/main/java/net/rptools/maptool/model/CampaignProperties.java index b9e007ba9c..0750a548ae 100644 --- a/src/main/java/net/rptools/maptool/model/CampaignProperties.java +++ b/src/main/java/net/rptools/maptool/model/CampaignProperties.java @@ -56,6 +56,8 @@ import net.rptools.maptool.server.proto.HaloListDto; import net.rptools.maptool.server.proto.LightSourceListDto; import net.rptools.maptool.server.proto.TokenPropertyListDto; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.CaseUtils; public class CampaignProperties implements Serializable { @@ -143,9 +145,9 @@ public CampaignProperties() {} public CampaignProperties(CampaignProperties properties) { for (Entry> entry : properties.tokenTypeMap.entrySet()) { - List typeList = new ArrayList<>(properties.tokenTypeMap.get(entry.getKey())); + List typeProperties = new ArrayList<>(properties.tokenTypeMap.get(entry.getKey())); - tokenTypeMap.put(entry.getKey(), typeList); + tokenTypeMap.put(entry.getKey(), typeProperties); } tokenTypeStatSheetMap.putAll(properties.tokenTypeStatSheetMap); @@ -204,7 +206,7 @@ public Map> getTokenTypeMap() { } /** - * Returns the default stat sheet details for a token property type. + * Returns the default stat sheet details for a token's type properties. * * @param propertyType the token property type to get the details for. * @return the stat sheet details. @@ -592,46 +594,26 @@ private void initTokenTypeMap() { } List list = new ArrayList<>(); - list.add( - new TokenProperty( - I18N.getText(PROP_PREFIX + "strength"), I18N.getText(SHORT_PROP_PREFIX + "strength"))); - list.add( - new TokenProperty( - I18N.getText(PROP_PREFIX + "dexterity"), - I18N.getText(SHORT_PROP_PREFIX + "dexterity"))); - list.add( - new TokenProperty( - I18N.getText(PROP_PREFIX + "constitution"), - I18N.getText(SHORT_PROP_PREFIX + "constitution"))); - list.add( - new TokenProperty( - I18N.getText(PROP_PREFIX + "intelligence"), - I18N.getText(SHORT_PROP_PREFIX + "intelligence"))); - list.add( - new TokenProperty( - I18N.getText(PROP_PREFIX + "wisdom"), I18N.getText(SHORT_PROP_PREFIX + "wisdom"))); - list.add( - new TokenProperty( - I18N.getText(PROP_PREFIX + "charisma"), I18N.getText(SHORT_PROP_PREFIX + "charisma"))); - list.add(new TokenProperty(I18N.getText(PROP_PREFIX + "hp"), true, true, false)); - list.add(new TokenProperty(I18N.getText(PROP_PREFIX + "ac"), true, true, false)); - list.add( - new TokenProperty( - I18N.getText(PROP_PREFIX + "defense"), I18N.getText(SHORT_PROP_PREFIX + "defense"))); - list.add( - new TokenProperty( - I18N.getText(PROP_PREFIX + "movement"), I18N.getText(SHORT_PROP_PREFIX + "movement"))); - list.add( - new TokenProperty( - I18N.getText(PROP_PREFIX + "elevation"), - I18N.getText(SHORT_PROP_PREFIX + "elevation"), - true, - false, - false)); - list.add( - new TokenProperty( - I18N.getText(PROP_PREFIX + "description"), - I18N.getText(SHORT_PROP_PREFIX + "description"))); + final String[] basicPropNames = new String[]{"strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma", "hp", "ac", "defense", "movement", "elevation", "description"}; + for(String propName : basicPropNames){ + PermissionsScope visibility = switch (propName){ + case "hp", "ac" -> PermissionsScope.OWNER; + case "elevation", "description" -> PermissionsScope.ALL; + default -> PermissionsScope.NONE; + }; + + String displayName = I18N.getText(PROP_PREFIX + propName); + TokenProperty tp = new TokenProperty( + CaseUtils.toCamelCase(displayName, false), + I18N.getText(SHORT_PROP_PREFIX + propName), + displayName, + propName.equals("description") ? VariableType.STRING : VariableType.NUMBER, + visibility + ); + + + list.add(tp); + } tokenTypeMap.put(getDefaultTokenPropertyType(), list); } diff --git a/src/main/java/net/rptools/maptool/model/TokenProperty.java b/src/main/java/net/rptools/maptool/model/TokenProperty.java index 40ddd86506..342135922f 100644 --- a/src/main/java/net/rptools/maptool/model/TokenProperty.java +++ b/src/main/java/net/rptools/maptool/model/TokenProperty.java @@ -15,161 +15,343 @@ package net.rptools.maptool.model; import com.google.protobuf.StringValue; + import java.io.Serializable; +import java.util.Objects; + import net.rptools.maptool.server.proto.TokenPropertyDto; -public class TokenProperty implements Serializable { - private String name; - private String shortName; - private boolean highPriority; // showOnStatSheet; so that 1.3b28 files load in 1.3b29 - private boolean ownerOnly; - private boolean gmOnly; - private String defaultValue; - - private String displayName; - - public TokenProperty() { - // For serialization - } - - public TokenProperty(String name) { - this(name, null, false, false, false); - } - - public TokenProperty(String name, String shortName) { - this(name, shortName, false, false, false); - } - - public TokenProperty(String name, boolean highPriority, boolean isOwnerOnly, boolean isGMOnly) { - this(name, null, highPriority, isOwnerOnly, isGMOnly); - } - - public TokenProperty( - String name, String shortName, boolean highPriority, boolean isOwnerOnly, boolean isGMOnly) { - this.name = name; - this.shortName = shortName; - this.highPriority = highPriority; - this.ownerOnly = isOwnerOnly; - this.gmOnly = isGMOnly; - } - - public TokenProperty( - String name, - String shortName, - boolean highPriority, - boolean isOwnerOnly, - boolean isGMOnly, - String defaultValue) { - this.name = name; - this.shortName = shortName; - this.highPriority = highPriority; - this.ownerOnly = isOwnerOnly; - this.gmOnly = isGMOnly; - this.defaultValue = defaultValue; - } - - /** - * Creates a new TokenProperty that's a copy of another. - * - * @param prop the property to copy the values from. - */ - public TokenProperty(TokenProperty prop) { - this.name = prop.name; - this.shortName = prop.shortName; - this.highPriority = prop.highPriority; - this.ownerOnly = prop.ownerOnly; - this.gmOnly = prop.gmOnly; - this.defaultValue = prop.defaultValue; - this.displayName = prop.displayName; - } - - public boolean isOwnerOnly() { - return ownerOnly; - } - - public void setOwnerOnly(boolean ownerOnly) { - this.ownerOnly = ownerOnly; - } - - public boolean isShowOnStatSheet() { - return highPriority; - } - - public void setShowOnStatSheet(boolean showOnStatSheet) { - this.highPriority = showOnStatSheet; - } - - public String getName() { - return name; - } - - public boolean hasDisplayName() { - return displayName != null; - } - - public String getDisplayName() { - return displayName; - } - - public void setName(String name) { - this.name = name; - } - - public String getShortName() { - return shortName; - } - - public void setShortName(String shortName) { - this.shortName = shortName; - } - - public boolean isGMOnly() { - return gmOnly; - } - - public void setGMOnly(boolean gmOnly) { - this.gmOnly = gmOnly; - } - - public String getDefaultValue() { - return this.defaultValue; - } - - public void setDefaultValue(String defaultValue) { - this.defaultValue = defaultValue; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - public static TokenProperty fromDto(TokenPropertyDto dto) { - var prop = new TokenProperty(); - prop.name = dto.getName(); - prop.shortName = dto.hasShortName() ? dto.getShortName().getValue() : null; - prop.highPriority = dto.getHighPriority(); - prop.ownerOnly = dto.getOwnerOnly(); - prop.gmOnly = dto.getGmOnly(); - prop.defaultValue = dto.hasDefaultValue() ? dto.getDefaultValue().getValue() : null; - prop.displayName = dto.hasDisplayName() ? dto.getDisplayName().getValue() : null; - return prop; - } - - public TokenPropertyDto toDto() { - var dto = TokenPropertyDto.newBuilder(); - dto.setName(name); - if (shortName != null) { - dto.setShortName(StringValue.of(shortName)); - } - dto.setHighPriority(highPriority); - dto.setOwnerOnly(ownerOnly); - dto.setGmOnly(gmOnly); - if (defaultValue != null) { - dto.setDefaultValue(StringValue.of(defaultValue)); - } - if (hasDisplayName()) { - dto.setDisplayName(StringValue.of(displayName)); - } - return dto.build(); - } +public class TokenProperty implements DisplayNames, Serializable { + private String name; + private String shortName; + private String displayName; + private boolean playerEditable = true; + private PermissionsScope visibilityPermission = PermissionsScope.NONE; + private VariableType variableType = VariableType.UNDEFINED; + private String defaultValue = ""; + + public TokenProperty() { + // For serialization + } + + public TokenProperty(String name) { + this(name, null, (String) null); + } + + public TokenProperty(String name, String shortName) { + this(name, shortName, (String) null); + } + public TokenProperty(String name, String shortName, String displayName) { + this.name = name; + this.shortName = shortName; + this.displayName = displayName; + } + public TokenProperty(String name, VariableType variableType) { + this(name, null, null, true, variableType, PermissionsScope.NONE, null); + } + + public TokenProperty(String name, boolean playerEditable) { + this(name, null, null, playerEditable, null, PermissionsScope.NONE, null); + } + + public TokenProperty(String name, PermissionsScope visibilityPermission) { + this(name, null, null, true, null, visibilityPermission, null); + } + + public TokenProperty(String name, boolean playerEditable, VariableType variableType) { + this(name, null, null, playerEditable, variableType, PermissionsScope.NONE, null); + } + + public TokenProperty(String name, String shortName, VariableType variableType) { + this(name, shortName, null, true, variableType, PermissionsScope.NONE, null); + } + + public TokenProperty(String name, String shortName, boolean playerEditable) { + this(name, shortName, null, playerEditable, null, PermissionsScope.NONE, null); + } + + public TokenProperty(String name, String shortName, boolean playerEditable, VariableType variableType) { + this(name, shortName, null, playerEditable, variableType, PermissionsScope.NONE, null); + } + + + public TokenProperty(String name, String shortName, String displayName, VariableType variableType) { + this(name, shortName, displayName, true, variableType, PermissionsScope.NONE, null); + } + + public TokenProperty(String name, String shortName, String displayName, boolean playerEditable) { + this(name, shortName, displayName, playerEditable, null, PermissionsScope.NONE, null); + } + + public TokenProperty(String name, String shortName, String displayName, boolean playerEditable, VariableType variableType) { + this(name, shortName, displayName, playerEditable, variableType, PermissionsScope.NONE, null); + } + + public TokenProperty(String name, VariableType variableType, PermissionsScope visibilityPermission) { + this(name, null, null, true, variableType, visibilityPermission, null); + } + + public TokenProperty(String name, boolean playerEditable, PermissionsScope visibilityPermission) { + this(name, null, null, playerEditable, null, visibilityPermission, null); + } + + public TokenProperty(String name, boolean playerEditable, VariableType variableType, PermissionsScope visibilityPermission) { + this(name, null, null, playerEditable, variableType, visibilityPermission, null); + } + + public TokenProperty(String name, String shortName, PermissionsScope visibilityPermission) { + this(name, shortName, null, true, null, visibilityPermission, null); + } + + public TokenProperty(String name, String shortName, VariableType variableType, PermissionsScope visibilityPermission) { + this(name, shortName, null, true, variableType, visibilityPermission, null); + } + + public TokenProperty( + String name, + String shortName, + boolean playerEditable, + PermissionsScope visibilityPermission) { + this(name, shortName, null, playerEditable, null, visibilityPermission, null); + } + + public TokenProperty( + String name, + String shortName, + boolean playerEditable, + VariableType variableType, + PermissionsScope visibilityPermission) { + this(name, shortName, null, playerEditable, variableType, visibilityPermission, null); + } + + public TokenProperty( + String name, String shortName, PermissionsScope visibilityPermission, String defaultValue) { + this(name, shortName, null, true, null, visibilityPermission, defaultValue); + } + + public TokenProperty( + String name, String shortName, VariableType variableType, PermissionsScope visibilityPermission, String defaultValue) { + this(name, shortName, null, true, variableType, visibilityPermission, defaultValue); + } + + public TokenProperty( + String name, + String shortName, + boolean playerEditable, + PermissionsScope visibilityPermission, + String defaultValue) { + this(name, shortName, null, playerEditable, null, visibilityPermission, defaultValue); + } + + public TokenProperty( + String name, + String shortName, + boolean playerEditable, + VariableType variableType, + PermissionsScope visibilityPermission, + String defaultValue) { + this(name, shortName, null, playerEditable, variableType, visibilityPermission, defaultValue); + } + + public TokenProperty( + String name, String shortName, String displayName, PermissionsScope visibilityPermission) { + this(name, shortName, displayName, true, null, visibilityPermission, null); + } + + public TokenProperty( + String name, String shortName, String displayName, VariableType variableType, PermissionsScope visibilityPermission) { + this(name, shortName, displayName, true, variableType, visibilityPermission, null); + } + + public TokenProperty( + String name, + String shortName, + String displayName, + boolean playerEditable, + PermissionsScope visibilityPermission) { + this(name, shortName, displayName, playerEditable, null, visibilityPermission, null); + } + + public TokenProperty( + String name, + String shortName, + String displayName, + boolean playerEditable, + VariableType variableType, + PermissionsScope visibilityPermission) { + this(name, shortName, displayName, playerEditable, variableType, visibilityPermission, null); + } + + public TokenProperty( + String name, + String shortName, + String displayName, + boolean playerEditable, + VariableType variableType, + PermissionsScope visibilityPermission, + String defaultValue) { + this.name = name; + this.shortName = shortName; + this.displayName = displayName; + this.playerEditable = playerEditable; + if (variableType != null) { + this.variableType = variableType; + } + if (visibilityPermission != null) { + this.visibilityPermission = visibilityPermission; + } + this.defaultValue = defaultValue; + } + + /** + * Creates a new TokenProperty that's a copy of another. + * + * @param prop the property to copy the values from. + */ + public TokenProperty(TokenProperty prop) { + this.name = prop.name; + this.shortName = prop.shortName; + this.displayName = prop.displayName; + this.playerEditable = prop.playerEditable; + this.variableType = prop.variableType; + this.visibilityPermission = prop.visibilityPermission; + this.defaultValue = prop.defaultValue; + } + + public static TokenProperty fromDto(TokenPropertyDto dto) { + var prop = new TokenProperty(); + prop.name = dto.getName(); + prop.shortName = dto.hasShortName() ? dto.getShortName().getValue() : null; + + prop.playerEditable = !dto.hasPlayerEditable() || dto.getPlayerEditable(); + prop.variableType = !dto.hasVariableType() ? VariableType.UNDEFINED : VariableType.valueOf(dto.getVariableType()); + + if (dto.hasPermissions()) { + // the new permissions + prop.visibilityPermission = PermissionsScope.valueOf(dto.getPermissions()); + } else if (dto.hasHighPriority() && dto.getHighPriority()) { + // the old permissions + if (dto.hasGmOnly() && dto.getGmOnly()) { + prop.visibilityPermission = PermissionsScope.GM; + } else if (dto.hasOwnerOnly() && dto.getOwnerOnly()) { + prop.visibilityPermission = PermissionsScope.OWNER; + } else { + prop.visibilityPermission = PermissionsScope.ALLIED_ONLY; + } + } + + prop.defaultValue = dto.hasDefaultValue() ? dto.getDefaultValue().getValue() : null; + prop.displayName = dto.hasDisplayName() ? dto.getDisplayName().getValue() : null; + return prop; + } + + public boolean isShowOnStatSheet() { + return visibilityPermission != null + && !visibilityPermission.equals(PermissionsScope.NONE); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean hasDisplayName() { + return displayName != null && !displayName.isBlank(); + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public boolean hasShortName() { + return shortName != null && !shortName.isBlank(); + } + + public String getShortName() { + return shortName; + } + + public void setShortName(String shortName) { + this.shortName = shortName; + } + + public boolean isPlayerEditable() { + return playerEditable; + } + + public void setPlayerEditable(boolean playerEditable) { + this.playerEditable = playerEditable; + } + + public PermissionsScope getVisibilityPermission() { + if(visibilityPermission == null){ + visibilityPermission = PermissionsScope.NONE; + } + return visibilityPermission; + } + + public void setVisibilityPermission(PermissionsScope visibilityPermission) { + this.visibilityPermission = visibilityPermission; + } + + public boolean isGMOnly() { + return visibilityPermission.equals(PermissionsScope.GM); + } + + public boolean isOwnerOnly() { + return visibilityPermission.equals(PermissionsScope.OWNER); + } + + public boolean isAllyOnly() { + return visibilityPermission.equals(PermissionsScope.ALLIED_ONLY); + } + + public boolean hasDefaultValue() { + return defaultValue != null && !defaultValue.isBlank(); + } + + public String getDefaultValue() { + return this.defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + + public VariableType getVariableType() { + if(variableType == null){ + variableType = VariableType.UNDEFINED; + } + return variableType; + } + + public void setVariableType(VariableType variableType) { + this.variableType = variableType; + } + + public TokenPropertyDto toDto() { + var dto = TokenPropertyDto.newBuilder(); + dto.setName(name); + dto.setPlayerEditable(playerEditable); + dto.setVariableType(Objects.requireNonNullElse(variableType, VariableType.UNDEFINED).name()); + dto.setPermissions(Objects.requireNonNullElse(visibilityPermission, PermissionsScope.NONE).name()); + if (hasShortName()) { + dto.setShortName(StringValue.of(shortName)); + } + if (hasDisplayName()) { + dto.setDisplayName(StringValue.of(displayName)); + } + if (hasDefaultValue()) { + dto.setDefaultValue(StringValue.of(defaultValue)); + } + return dto.build(); + } } diff --git a/src/main/java/net/rptools/maptool/model/VariableType.java b/src/main/java/net/rptools/maptool/model/VariableType.java new file mode 100644 index 0000000000..2f0c09c672 --- /dev/null +++ b/src/main/java/net/rptools/maptool/model/VariableType.java @@ -0,0 +1,33 @@ +package net.rptools.maptool.model; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.rptools.maptool.language.I18N; + +public enum VariableType { + UNDEFINED("variable.type.undefined", String.class), + JSON("variable.type.json", JsonElement.class), + JSON_ARRAY("variable.type.jsonArray", JsonArray.class), + JSON_OBJECT("variable.type.jsonObject", JsonObject.class), + NUMBER("variable.type.number", Number.class), + STRING("variable.type.string", String.class), + STRING_LIST("variable.type.stringList", String.class), + STRING_PROP_LIST("variable.type.stringPropList", String.class), + ; + final String displayName; + final Class klass; + VariableType(String i18nKey, Class klass){ + this.displayName = I18N.getText(i18nKey); + this.klass = klass; + } + + public Class getKlass() { + return klass; + } + + @Override + public String toString() { + return displayName; + } +} 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 87d475b9c9..b400c749b3 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 @@ -32,6 +32,9 @@ import net.rptools.maptool.client.events.TokenHoverEnter; import net.rptools.maptool.client.ui.token.AbstractTokenOverlay; import net.rptools.maptool.client.ui.token.BarTokenOverlay; +import net.rptools.maptool.model.PermissionsScope; +import net.rptools.maptool.model.Token; +import net.rptools.maptool.model.TokenProperty; import net.rptools.maptool.model.player.Player; import net.rptools.maptool.util.HTMLUtil; import net.rptools.maptool.util.ImageManager; @@ -164,19 +167,19 @@ public String getShortName() { /** The notes of the token. */ private final String notes; - /** The notes type of the token. */ + /** The token notes type. */ private final String notesType; /** The GM notes of the token. */ private final String gmNotes; - /** The GM notes type of the token. */ + /** The token's GM notes type. */ private final String gmNotesType; /** The speech name of the token. */ private final String speechName; - /** The type of the token. */ + /** The token type. */ private final String tokenType; /** True if the player is a GM. */ @@ -247,21 +250,10 @@ public StatSheetContext(TokenHoverEnter hoverEvent, Player player, StatSheetLoca .getTokenPropertyList(token.getPropertyType()) .forEach( tp -> { - if (tp.isShowOnStatSheet()) { - if (tp.isGMOnly() && !playerIsGm) { - return; - } - - if (tp.isOwnerOnly() && !playerOwns) { - return; - } - + if (isPermitted(token, player, tp)) { Object value = token.getEvaluatedProperty(resolver, tp.getName()); - if (value == null) { - return; - } - - if (value instanceof String sValue && sValue.isBlank()) { + //noinspection ConstantValue + if (value == null || value instanceof String sValue && sValue.isBlank()) { return; } properties.add( @@ -297,6 +289,46 @@ public StatSheetContext(TokenHoverEnter hoverEvent, Player player, StatSheetLoca }; } + /** + * Simplistic check for alliance based on GM vs. players. With all players are on the same side, + * all PCs and player-owned NPCs are on the same side. + * + * @param token to check is ally + * @param player whose side we are checking for + * @return if token belongs to the same side + */ + private boolean isAllied(Token token, Player player) { + if (!player.isGM() && token.getType().equals(Token.Type.PC)) { + return true; + } else { + List teamMates = MapTool.getPlayerList().stream() + .dropWhile(p -> p.isGM() != player.isGM()) + .toList(); + final Set owners = token.getOwners(); + return !teamMates.stream().filter(p -> owners.contains(p.getName())).toList().isEmpty(); + } + } + + private boolean isPermitted(Token token, Player player, Object checkThis) { + if (checkThis instanceof TokenProperty tp) { + PermissionsScope permission = tp.getVisibilityPermission(); + return switch (permission) { + case ALL -> true; + case NONE -> false; + case GM -> player.isGM(); + case OWNER -> player.isGM() || token.isOwner(player.getName()); + case ALLIED -> player.isGM() || token.isOwner(player.getName()) || isAllied(token, player); + case OWNER_ONLY -> !player.isGM() && token.isOwner(player.getName()); + case ALLIED_ONLY -> !player.isGM() && !token.isOwner(player.getName()) && isAllied(token, player); + case OPPONENT_ONLY -> !player.isGM() && !token.isOwner(player.getName()) && !isAllied(token, player); + case null -> false; + }; + } else { + // not yet implemented + return false; + } + } + private static final Function getImageDimensions = md5Key -> { BufferedImage image = ImageManager.getImage(md5Key); @@ -433,6 +465,14 @@ public String getImage() { public String getPortrait() { return portraitAsset != null ? "asset://" + portraitAsset : null; } +/** + * Returns the handout asset of the token. + * + * @return The portrait asset of the token. + */ + public String getHandout() { + return handoutAsset != null ? "asset://" + handoutAsset : null; + } /** * Returns the label of the token. @@ -515,9 +555,9 @@ public String getSpeechName() { } /** - * Returns the type of the token. + * Returns the token type. * - * @return The type of the token. + * @return The token type. */ public String getTokenType() { return tokenType; diff --git a/src/main/resources/net/rptools/maptool/client/ui/themes/AahLAF.properties b/src/main/resources/net/rptools/maptool/client/ui/themes/AahLAF.properties index 274ed441c4..c01e9b5a37 100644 --- a/src/main/resources/net/rptools/maptool/client/ui/themes/AahLAF.properties +++ b/src/main/resources/net/rptools/maptool/client/ui/themes/AahLAF.properties @@ -105,7 +105,7 @@ ToggleButton.toolbar.disabledSelectedForeground = $Button.disabledSelectedForegr ToggleButton.toolbar.disabledSelectedBackground = $Button.disabledSelectedBackground Button.toolbar.margin = 3,3,3,3 -Button.toolbar.spacingInsets = 1,2,1,0 +Button.toolbar.spacingInsets = 1,2,1,0 CheckBox.border = 3,3,5,3, #a3964d, 1, 6 @@ -115,7 +115,7 @@ Component.borderWidth = 1 Component.focusWidth = 1 Component.focusedBorderColor = mix(@gradientBorderStart, @gradientBorderEnd, 80%) Component.innerFocusWidth = 0 -Component.titleBarCaption = true +Component.titleBarCaption = true HelpButton.innerFocusWidth = 1 HelpButton.questionMarkColor = @blue @@ -199,6 +199,47 @@ TabbedPane.tabSeparatorsFullHeight= true TabbedPane.underlineColor = @col50 TabbedPane.hoverColor = @col0 +Table.cellMargins = 3,3,3,3 +# Table.selectionInsets +Table.background = @col5 +Table.foreground = @col80 +Table.selectionBackground = @col10 +Table.selectionForeground= @col90 +# Table.selectionInactiveBackground +# Table.selectionInactiveForeground +# Table.alternateRowColor = @col5 +Table.gridColor = @col50 +Table.cellFocusColor = @blue +# Table.focusCellForeground +# Table.focusCellBackground +# Table.dropLineColor +# Table.dropLineShortColor +# Table.dropCellBackground +# Table.dropCellForeground +Table.showHorizontalLines = true +Table.showVerticalLines = true +Table.showTrailingVerticalLine = false +Table.showCellFocusIndicator = true + +# Table.cellBorder = 0,0,0,0, 1 +# Table.focusCellHighlightBorder = 1,1,1,1 @red 1 +Table.focusSelectedCellHighlightBorder = 3,3,3,3, @red, 2, 6 +Table.paintOutsideAlternateRows = true +# Table.editorSelectAllOnStartEditing +# Table.consistentHomeEndKeyBehavior + + + +TableHeader.background = @col20 +TableHeader.foreground = @col80 +# TableHeader.hoverBackground +# TableHeader.hoverForeground +# TableHeader.pressedBackground +# TableHeader.pressedForeground +Table.sortIconColor = @blue +TableHeader.separatorColor = @col40 +TableHeader.bottomSeparatorColor = @col60 + TextComponent.arc = 8 ToolBar.background = @col5 diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 99a954df44..ad791acf76 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -216,6 +216,15 @@ Color.none = None Color.custom = Custom Color.default = Default +variable.type.undefined = Undefined +variable.type.json = JSON +variable.type.jsonArray = JSON Array +variable.type.jsonObject = JSON Object +variable.type.number = Number +variable.type.string = String +variable.type.stringList = String List +variable.type.stringPropList = String Property List + Default.campaign.tokenPropertyType = Basic Default.campaign.tokenProperty.name.strength = Strength Default.campaign.tokenProperty.name.dexterity = Dexterity @@ -223,8 +232,8 @@ Default.campaign.tokenProperty.name.constitution = Constitution Default.campaign.tokenProperty.name.intelligence = Intelligence Default.campaign.tokenProperty.name.wisdom = Wisdom Default.campaign.tokenProperty.name.charisma = Charisma -Default.campaign.tokenProperty.name.hp = HP -Default.campaign.tokenProperty.name.ac = AC +Default.campaign.tokenProperty.name.hp = Hit Points +Default.campaign.tokenProperty.name.ac = Armour Class Default.campaign.tokenProperty.name.defense = Defense Default.campaign.tokenProperty.name.movement = Movement Default.campaign.tokenProperty.name.elevation = Elevation @@ -235,6 +244,8 @@ Default.campaign.tokenProperty.name.short.constitution = Con Default.campaign.tokenProperty.name.short.intelligence = Int Default.campaign.tokenProperty.name.short.wisdom = Wis Default.campaign.tokenProperty.name.short.charisma = Cha +Default.campaign.tokenProperty.name.short.hp = HP +Default.campaign.tokenProperty.name.short.ac = AC Default.campaign.tokenProperty.name.short.defense = Def Default.campaign.tokenProperty.name.short.movement = Mov Default.campaign.tokenProperty.name.short.elevation = Elv @@ -1057,6 +1068,23 @@ Button.networkingHelp = Networking Help Button.networkingHelp.mnemonic = {f1} ServerDialog.generatePassword = Generate Password +permissionsScope.displayName.none = None +permissionsScope.displayName.gm = GM +permissionsScope.displayName.owner = Owner +permissionsScope.displayName.allies = Allies +permissionsScope.displayName.all = All +permissionsScope.displayName.owner.discrete = Owner Only +permissionsScope.displayName.allies.discrete = Allies Only +permissionsScope.displayName.opponent = Opponents + +permissionsScope.description.none = Nobody has permission. +permissionsScope.description.gm = Only the GM has permission. +permissionsScope.description.owner = The GM and Owner have permission. +permissionsScope.description.allies = The GM, Owner and their Allies have permission. +permissionsScope.description.all = Everyone has permission. +permissionsScope.description.owner.discrete = Only the Owner has permission. +permissionsScope.description.allies.discrete = Only the Owner's Allies have permission. +permissionsScope.description.opponent = Only the Owner's Opponents have permission. CampaignPropertiesDialog.tab.token = Token Properties CampaignPropertiesDialog.tab.repo = Repositories @@ -1085,18 +1113,18 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat Sheet -campaignPropertiesTable.column.gmStatSheet = GM -campaignPropertiesTable.column.ownerStatSheet = Owner +campaignPropertiesTable.column.playerEditable = Player Editable +campaignPropertiesTable.column.statSheetVisibility = Stat-Sheet campaignPropertiesTable.column.defaultValue = Default +campaignPropertiesTable.column.valueType = Value Type campaignPropertiesTable.column.name.description = The actual name of the property, used by the application. campaignPropertiesTable.column.shortName.description = Short version of the property name. Used in stat-sheets and character sheets. campaignPropertiesTable.column.displayName.description = Display version of the property name. Used in stat-sheets and character sheets. -campaignPropertiesTable.column.statSheet.description = Show the property on the stat-sheet. -campaignPropertiesTable.column.gm.description = Restrict visibility to the GM view of the stat-sheet. -campaignPropertiesTable.column.owner.description = Restrict visibility to the owner's view of the stat-sheet. +campaignPropertiesTable.column.statSheet.playerEditable = Show the property to players in the Edit Token dialog. +campaignPropertiesTable.column.statSheet.description = Define who has permission to see the property on the stat-sheet. campaignPropertiesTable.column.default.description = The default value assigned to the property. +campaignPropertiesTable.column.valueType.description = The type of value, e.g. number/JSON. # Bar propertyType CampaignPropertiesDialog.combo.bars.type.twoImages = Two Images 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 5830329fe6..dd6956304c 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat Sheet +campaignPropertiesTable.column.statSheetVisibility = Stat Sheet campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = Owner campaignPropertiesTable.column.defaultValue = Default 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 e583490f91..11442e3fec 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat Sheet +campaignPropertiesTable.column.statSheetVisibility = Stat Sheet campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = Owner campaignPropertiesTable.column.defaultValue = Default 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 c155aea777..623d406101 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Vorgabewert für diese Eigens campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Kurzname campaignPropertiesTable.column.displayName = Anzeigename -campaignPropertiesTable.column.onStatSheet = Werteblatt +campaignPropertiesTable.column.statSheetVisibility = Werteblatt campaignPropertiesTable.column.gmStatSheet = SL campaignPropertiesTable.column.ownerStatSheet = Besitzer campaignPropertiesTable.column.defaultValue = Vorgabe 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 2b55dc952f..4d7e8d31c1 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 @@ -889,6 +889,18 @@ ServerDialog.option.rolls.tooltip = Tool Tips will be used for [ ] rolls wh ServerDialog.button.networkinghelp = Networking Help ServerDialog.generatePassword = Generate Password +permissionsScope.displayName.none=None +permissionsScope.displayName.gm=GM +permissionsScope.displayName.owner=Owner +permissionsScope.displayName.allies=Allies +permissionsScope.displayName.all=All + +permissionsScope.description.none=Nobody has permission. +permissionsScope.description.owner=The Owner also has permission. +permissionsScope.description.gm=Only the GM has permission. +permissionsScope.description.allies=The Owner's Allies also have permission. +permissionsScope.description.all=Everyone has permission. + CampaignPropertiesDialog.tab.token = Token Properties CampaignPropertiesDialog.tab.repo = Repositories @@ -916,18 +928,18 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat-Sheet -campaignPropertiesTable.column.gmStatSheet = GM -campaignPropertiesTable.column.ownerStatSheet = Owner +campaignPropertiesTable.column.statSheetVisibility = Stat-Sheet View campaignPropertiesTable.column.defaultValue = Default +campaignPropertiesTable.column.playerEditable = Player Editable campaignPropertiesTable.column.name.description = The actual name of the property, used by the application. campaignPropertiesTable.column.shortName.description = Short version of the property name. Used in stat-sheets and character sheets. campaignPropertiesTable.column.displayName.description = Display version of the property name. Used in stat-sheets and character sheets. -campaignPropertiesTable.column.statSheet.description = Show the property on the stat-sheet. -campaignPropertiesTable.column.gm.description = Restrict visibility to the GM view of the stat-sheet. -campaignPropertiesTable.column.owner.description = Restrict visibility to the owner's view of the stat-sheet. +campaignPropertiesTable.column.statSheet.description = Define who has permission to see the property on the stat-sheet. campaignPropertiesTable.column.default.description = The default value assigned to the property. +campaignPropertiesTable.column.statSheet.playerEditable = Show the property to players in the Edit Token dialogue. + + # Bar propertyType CampaignPropertiesDialog.combo.bars.type.twoImages = Two Images @@ -2933,4 +2945,4 @@ advanced.roll.propertyNotNumber = Property {0} is not a number. advanced.roll.noTokenInContext = No token in context. advanced.roll.inputNotNumber = Input {0} is not a number. Preferences.label.tokens.stack.hide=Hide token stack indicator -Preferences.label.tokens.stack.hide.tooltip=Token Layer stack indicator will be hidden +Preferences.label.tokens.stack.hide.tooltip=Token Layer stack indicator will be hidden \ No newline at end of file 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 03444cebcc..5dc69d5867 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat-Sheet +campaignPropertiesTable.column.statSheetVisibility = Stat-Sheet campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = Owner campaignPropertiesTable.column.defaultValue = Default 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 317a541cc0..860b0a9c34 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat Sheet +campaignPropertiesTable.column.statSheetVisibility = Stat Sheet campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = Owner campaignPropertiesTable.column.defaultValue = Default 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 e86c59731b..def0b29993 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat Sheet +campaignPropertiesTable.column.statSheetVisibility = Stat Sheet campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = Owner campaignPropertiesTable.column.defaultValue = Default 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 7efef2dc07..08717837a9 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat Sheet +campaignPropertiesTable.column.statSheetVisibility = Stat Sheet campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = Owner campaignPropertiesTable.column.defaultValue = Default 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 07ea475e79..859f921dcc 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = 特性値の初期値 campaignPropertiesTable.column.name = 名称 campaignPropertiesTable.column.shortName = 短縮名 campaignPropertiesTable.column.displayName = 表示名称 -campaignPropertiesTable.column.onStatSheet = シート +campaignPropertiesTable.column.statSheetVisibility = シート campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = 所有 campaignPropertiesTable.column.defaultValue = 初期値 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 d108b271dc..ff2ba4c6d0 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Standaard waarde voor eigensc campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat Sheet +campaignPropertiesTable.column.statSheetVisibility = Stat Sheet campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = Eigenaar campaignPropertiesTable.column.defaultValue = Default 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 71a3d2eb4a..298463b1b4 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Domyślna wartość dla wła campaignPropertiesTable.column.name = Nazwa campaignPropertiesTable.column.shortName = Skrócona Nazwa campaignPropertiesTable.column.displayName = Wyświetlana nazwa -campaignPropertiesTable.column.onStatSheet = Arkusz statystyk +campaignPropertiesTable.column.statSheetVisibility = Arkusz statystyk campaignPropertiesTable.column.gmStatSheet = MG campaignPropertiesTable.column.ownerStatSheet = Właściciel campaignPropertiesTable.column.defaultValue = Domyślny 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 6494d2e403..4406a53e74 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat Sheet +campaignPropertiesTable.column.statSheetVisibility = Stat Sheet campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = Owner campaignPropertiesTable.column.defaultValue = Default 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 6009213980..a8266c2e8a 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Значение параме campaignPropertiesTable.column.name = Название campaignPropertiesTable.column.shortName = Сокращение campaignPropertiesTable.column.displayName = Отображаемое название -campaignPropertiesTable.column.onStatSheet = Блок пар. +campaignPropertiesTable.column.statSheetVisibility = Блок пар. campaignPropertiesTable.column.gmStatSheet = ГМ campaignPropertiesTable.column.ownerStatSheet = Владелец campaignPropertiesTable.column.defaultValue = По умолчанию 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 d8fac82570..cf5e5125b2 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Default value for property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat-Sheet +campaignPropertiesTable.column.statSheetVisibility = Stat-Sheet campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = Owner campaignPropertiesTable.column.defaultValue = Default 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 e4d2da76a1..7e20c2eebd 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat Sheet +campaignPropertiesTable.column.statSheetVisibility = Stat Sheet campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = Owner campaignPropertiesTable.column.defaultValue = Default 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 a4b066ccaf..6c4f55689a 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat Sheet +campaignPropertiesTable.column.statSheetVisibility = Stat Sheet campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = Owner campaignPropertiesTable.column.defaultValue = Default 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 88fbc2959b..47711e8780 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 @@ -916,7 +916,7 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name -campaignPropertiesTable.column.onStatSheet = Stat Sheet +campaignPropertiesTable.column.statSheetVisibility = Stat Sheet campaignPropertiesTable.column.gmStatSheet = GM campaignPropertiesTable.column.ownerStatSheet = Owner campaignPropertiesTable.column.defaultValue = Default diff --git a/src/test/java/net/rptools/maptool/model/TokenPropertiesTest.java b/src/test/java/net/rptools/maptool/model/TokenPropertiesTest.java index 09a477e6f4..e66e3c1662 100644 --- a/src/test/java/net/rptools/maptool/model/TokenPropertiesTest.java +++ b/src/test/java/net/rptools/maptool/model/TokenPropertiesTest.java @@ -37,21 +37,19 @@ public class TokenPropertiesTest { @BeforeEach public void setUp() { propsList = new ArrayList<>(); - propsList.add(new TokenProperty("prop1", null, true, false, false, "10")); - propsList.add(new TokenProperty("prop2", null, true, false, false, "{prop2=prop1}")); + propsList.add(new TokenProperty("prop1", null, null, null, "10")); + propsList.add(new TokenProperty("prop2", null, null, PermissionsScope.ALL, "{prop2=prop1}")); propsList.add( new TokenProperty( "jsonObj1", null, - true, - false, - false, + PermissionsScope.ALL, "{\"sampleKey\": 5, \"otherKey\": \"theValue\"}")); - propsList.add(new TokenProperty("jsonObj2", null, true, false, false, "{\"prop3\"=other}")); - propsList.add(new TokenProperty("jsonObj3", null, true, false, false, "{prop3:other}")); - propsList.add(new TokenProperty("jsonArr1", null, true, false, false, "[4, 3]")); - propsList.add(new TokenProperty("plainStr1", null, true, false, false, "justAString")); - propsList.add(new TokenProperty("badJson", null, true, false, false, "{\"a\": 1}{\"b\": 2}")); + propsList.add(new TokenProperty("jsonObj2", null, PermissionsScope.ALL, "{\"prop3\"=other}")); + propsList.add(new TokenProperty("jsonObj3", null, PermissionsScope.ALL, "{prop3:other}")); + propsList.add(new TokenProperty("jsonArr1", null, PermissionsScope.ALL, "[4, 3]")); + propsList.add(new TokenProperty("plainStr1", null, PermissionsScope.ALL, "justAString")); + propsList.add(new TokenProperty("badJson", null, PermissionsScope.ALL, "{\"a\": 1}{\"b\": 2}")); MapTool.getCampaign().putTokenType("testType", propsList); testToken = new Token(); From 0cfb4964d3bb482a5ab1820b0c682e23982a9952 Mon Sep 17 00:00:00 2001 From: bubblobill Date: Fri, 17 Jul 2026 15:53:32 +0800 Subject: [PATCH 02/12] Permissions implemented on TokenProperty for; - visibility on stat-sheet - visibility in token editor - editability in token editor. Updated DTOs. Updated TokenPropertiesTest Updated Token Editor Updated CampaignProperties Updated Campaign Properties Editor. Shuffled various cell editor/renderers off to a common home. Tweaked AahLAF to ensure it all looks good. --- .../main/proto/data_transfer_objects.proto | 6 +- .../swing/MultiLineTableHeaderRenderer.java | 38 - .../swing/TableCellRendererDecorator.java | 78 -- .../table/MTMultilineStringCellEditor.java | 134 ++++ .../table/MultiLineTableHeaderRenderer.java | 66 ++ .../TextFieldEditorButtonTableCellEditor.java | 2 +- .../swing/table/WordWrapCellRenderer.java | 64 ++ .../maptool/client/tool/PointerTool.java | 8 +- .../TokenPropertiesManagementPanel.java | 29 +- .../TokenPropertiesManagementPanelView.form | 110 ++- .../TokenPropertiesTableModel.java | 33 +- .../edit/TokenPropertiesEditorPanel.java | 552 ++++++--------- .../maptool/model/CampaignProperties.java | 41 +- .../rptools/maptool/model/TokenProperty.java | 668 +++++++++--------- .../rptools/maptool/model/VariableType.java | 33 - .../model/sheet/stats/StatSheetContext.java | 48 +- .../model/sheet/stats/StatSheetManager.java | 3 +- .../client/ui/themes/AahLAF.properties | 66 +- .../rptools/maptool/language/i18n.properties | 49 +- .../maptool/model/TokenPropertiesTest.java | 17 +- 20 files changed, 1060 insertions(+), 985 deletions(-) delete mode 100644 src/main/java/net/rptools/maptool/client/swing/MultiLineTableHeaderRenderer.java delete mode 100644 src/main/java/net/rptools/maptool/client/swing/TableCellRendererDecorator.java create mode 100644 src/main/java/net/rptools/maptool/client/swing/table/MTMultilineStringCellEditor.java create mode 100644 src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java rename src/main/java/net/rptools/maptool/client/swing/{ => table}/TextFieldEditorButtonTableCellEditor.java (98%) create mode 100644 src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java delete mode 100644 src/main/java/net/rptools/maptool/model/VariableType.java diff --git a/messages/src/main/proto/data_transfer_objects.proto b/messages/src/main/proto/data_transfer_objects.proto index 2f8218432d..563dbbfe85 100644 --- a/messages/src/main/proto/data_transfer_objects.proto +++ b/messages/src/main/proto/data_transfer_objects.proto @@ -208,9 +208,9 @@ message TokenPropertyDto { optional bool high_priority = 3; optional bool owner_only = 4; optional bool gm_only = 5; - optional string permissions = 8; - optional bool player_editable = 9; - optional string variable_type = 10; + optional string stat_sheet_view_permission = 8; + optional string editor_view_permission = 9; + optional string editor_edit_permission = 10; google.protobuf.StringValue default_value = 6; google.protobuf.StringValue display_name = 7; } diff --git a/src/main/java/net/rptools/maptool/client/swing/MultiLineTableHeaderRenderer.java b/src/main/java/net/rptools/maptool/client/swing/MultiLineTableHeaderRenderer.java deleted file mode 100644 index b69794da24..0000000000 --- a/src/main/java/net/rptools/maptool/client/swing/MultiLineTableHeaderRenderer.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.rptools.maptool.client.swing; - -import javax.swing.*; -import javax.swing.table.TableCellRenderer; -import java.awt.*; - -public class MultiLineTableHeaderRenderer implements TableCellRenderer { - private final Color fg, bg; - - public MultiLineTableHeaderRenderer() { - bg = UIManager.getDefaults().getColor("TableHeader.background"); - fg = UIManager.getDefaults().getColor("TableHeader.foreground"); - } - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - JPanel panel = new JPanel(); - panel.setBackground(bg); - panel.setForeground(fg); - LookAndFeel.installBorder(panel, "TableHeader.cellBorder"); - - BoxLayout box = new BoxLayout(panel, BoxLayout.PAGE_AXIS); - panel.setLayout(box); - - String[] heading = ((String)value).split(" "); - for(String word: heading){ - JLabel label = new JLabel(word, null, SwingConstants.CENTER); - label.setBackground(bg); - label.setForeground(fg); - label.setOpaque(false); - label.setAlignmentX(0.5f); - panel.add(label); - } - panel.invalidate(); - - return panel; - } -} \ No newline at end of file diff --git a/src/main/java/net/rptools/maptool/client/swing/TableCellRendererDecorator.java b/src/main/java/net/rptools/maptool/client/swing/TableCellRendererDecorator.java deleted file mode 100644 index d94e901afa..0000000000 --- a/src/main/java/net/rptools/maptool/client/swing/TableCellRendererDecorator.java +++ /dev/null @@ -1,78 +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.swing; - -import java.awt.Component; -import javax.swing.JLabel; -import javax.swing.JTable; -import javax.swing.SwingConstants; -import javax.swing.table.TableCellRenderer; - -/** - * A customizable, theme-respecting table cell renderer. - * - *

This is primarily intended for customizing header cells. Since Java does not expose the - * default header cell renderer implementation, we cannot simply create new instances and customize - * them as we can with regular table cells. This makes it difficult to customize header cells while - * respecting the UI theme. - * - *

This cell renderer allows customizing header cells by decorating the pre-existing header cell - * renderer, then modifying the component returned in {@link #getTableCellRendererComponent(JTable, - * Object, boolean, boolean, int, int)}. - * - *

For now, only the text alignment can be customized, but support for other properties may be - * added in the future. - */ -public class TableCellRendererDecorator implements TableCellRenderer { - private final TableCellRenderer decorated; - private int horizontalAlignment = SwingConstants.LEADING; - private int verticalAlignment = SwingConstants.CENTER; - - public TableCellRendererDecorator(TableCellRenderer decorated) { - this.decorated = decorated; - } - - public int getHorizontalAlignment() { - return horizontalAlignment; - } - - public void setHorizontalAlignment(int horizontalAlignment) { - this.horizontalAlignment = horizontalAlignment; - } - - public int getVerticalAlignment() { - return verticalAlignment; - } - - public void setVerticalAlignment(int verticalAlignment) { - this.verticalAlignment = verticalAlignment; - } - - @Override - public Component getTableCellRendererComponent( - JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - var component = - decorated.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - - /* Typical renderers use a label, which just so happens to be the component that allows us to - * support alignment as we want. */ - if (component instanceof JLabel label) { - label.setHorizontalAlignment(horizontalAlignment); - label.setVerticalAlignment(verticalAlignment); - } - - return component; - } -} diff --git a/src/main/java/net/rptools/maptool/client/swing/table/MTMultilineStringCellEditor.java b/src/main/java/net/rptools/maptool/client/swing/table/MTMultilineStringCellEditor.java new file mode 100644 index 0000000000..de81c1f489 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/swing/table/MTMultilineStringCellEditor.java @@ -0,0 +1,134 @@ +/* + * 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.swing.table; + +import com.jidesoft.combobox.MultilineStringExComboBox; +import com.jidesoft.combobox.PopupPanel; +import com.jidesoft.grid.MultilineStringCellEditor; +import com.jidesoft.plaf.basic.BasicExComboBoxUI; +import java.awt.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Locale; +import java.util.ResourceBundle; +import javax.swing.*; +import net.rptools.maptool.client.AppConstants; +import net.rptools.maptool.client.AppPreferences; +import net.rptools.maptool.language.I18N; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.fife.ui.rsyntaxtextarea.Theme; +import org.fife.ui.rtextarea.RTextScrollPane; + +/* Pop-up cell editor using RSyntaxTextArea. Used in Token Property editor. */ +public class MTMultilineStringCellEditor extends MultilineStringCellEditor { + private static final Logger log = LogManager.getLogger(MTMultilineStringCellEditor.class); + + public MTMultilineStringExComboBox createMultilineStringComboBox() { + MTMultilineStringExComboBox localMultilineStringExComboBox = new MTMultilineStringExComboBox(); + localMultilineStringExComboBox.setEditable(true); + localMultilineStringExComboBox.setUI(new BasicExComboBoxUI()); + return localMultilineStringExComboBox; + } + + /* needed to change the popup for properties */ + public static class MTMultilineStringExComboBox extends MultilineStringExComboBox { + final ResourceBundle a = ResourceBundle.getBundle("com.jidesoft.combobox.combobox"); + + public ResourceBundle getResourceBundle(Locale paramLocale) { + return ResourceBundle.getBundle("com.jidesoft.combobox.combobox", paramLocale); + } + + public PopupPanel createPopupComponent() { + return new MTMultilineStringPopupPanel( + getResourceBundle(Locale.getDefault()).getString("ComboBox.multilineStringTitle")); + } + } + + /* the property popup table */ + private static class MTMultilineStringPopupPanel extends PopupPanel { + private final RSyntaxTextArea j = createTextArea(); + + public MTMultilineStringPopupPanel() { + this(""); + } + + public MTMultilineStringPopupPanel(String paramString) { + this.setResizable(true); + /* Set the color style via Theme */ + try { + File themeFile = + new File( + AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); + Theme theme = Theme.load(new FileInputStream(themeFile)); + theme.apply(j); + + j.revalidate(); + } catch (IOException e) { + log.error("Error while loading multiline property editor theme", e); + } + JScrollPane localJScrollPane = new RTextScrollPane(j); + localJScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + localJScrollPane.setAutoscrolls(true); + localJScrollPane.setPreferredSize(new Dimension(300, 200)); + setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 5)); + setLayout(new BorderLayout()); + setTitle(paramString); + add(localJScrollPane, "Center"); + setDefaultFocusComponent(j); + j.setLineWrap(false); + JCheckBox wrapToggle = new JCheckBox(I18N.getString("EditTokenDialog.msg.wrap")); + wrapToggle.addActionListener(e -> j.setLineWrap(!j.getLineWrap())); + + DefaultComboBoxModel syntaxListModel = new DefaultComboBoxModel<>(); + syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_NONE); + syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_JSON); + syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE); + syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_HTML); + syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_XML); + JComboBox syntaxComboBox = new JComboBox<>(syntaxListModel); + syntaxComboBox.addActionListener( + e -> j.setSyntaxEditingStyle((String) syntaxComboBox.getSelectedItem())); + + add(syntaxComboBox, BorderLayout.BEFORE_FIRST_LINE); + add(wrapToggle, BorderLayout.AFTER_LAST_LINE); + } + + public Object getSelectedObject() { + return j.getText(); + } + + public void setSelectedObject(Object paramObject) { + if (paramObject != null) { + j.setText(paramObject.toString()); + } else { + j.setText(""); + } + } + + protected RSyntaxTextArea createTextArea() { + RSyntaxTextArea textArea = new RSyntaxTextArea(); + textArea.setUseFocusableTips(false); + textArea.setAnimateBracketMatching(true); + textArea.setBracketMatchingEnabled(true); + textArea.setLineWrap(false); + textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE); + return textArea; + } + } +} diff --git a/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java b/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java new file mode 100644 index 0000000000..10dd1a0823 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java @@ -0,0 +1,66 @@ +/* + * 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.swing.table; + +import com.formdev.flatlaf.FlatIconColors; +import com.formdev.flatlaf.util.ColorFunctions; +import java.awt.*; +import javax.swing.*; +import javax.swing.table.TableCellRenderer; + +public class MultiLineTableHeaderRenderer implements TableCellRenderer { + private static final Color HEADER_BACKGROUND = + UIManager.getDefaults().getColor("TableHeader.background"); + private static final Color HEADER_FOREGROUND = + UIManager.getDefaults().getColor("TableHeader.foreground"); + private static final Color HEADER_ALTERNATE_BACKGROUND; + private static final Color HEADER_ALTERNATE_FOREGROUND; + + static { + Color mixWith = UIManager.getColor(FlatIconColors.OBJECTS_BLACK_TEXT.key); + HEADER_ALTERNATE_BACKGROUND = ColorFunctions.mix(HEADER_BACKGROUND, mixWith, 0.94f); + HEADER_ALTERNATE_FOREGROUND = ColorFunctions.mix(HEADER_FOREGROUND, mixWith, 0.31f); + } + + public MultiLineTableHeaderRenderer() {} + + @Override + public Component getTableCellRendererComponent( + JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + JPanel panel = new JPanel(); + Color foreground = column % 2 == 0 ? HEADER_ALTERNATE_FOREGROUND : HEADER_FOREGROUND; + Color background = column % 2 == 0 ? HEADER_ALTERNATE_BACKGROUND : HEADER_BACKGROUND; + panel.setBackground(background); + panel.setForeground(foreground); + + LookAndFeel.installBorder(panel, "TableHeader.cellBorder"); + + BoxLayout box = new BoxLayout(panel, BoxLayout.PAGE_AXIS); + panel.setLayout(box); + + String[] heading = ((String) value).split(" "); + for (String word : heading) { + JLabel label = new JLabel(word, null, SwingConstants.CENTER); + label.setBackground(background); + label.setForeground(foreground); + label.setOpaque(true); + label.setAlignmentX(0.5f); + panel.add(label); + } + panel.invalidate(); + + return panel; + } +} diff --git a/src/main/java/net/rptools/maptool/client/swing/TextFieldEditorButtonTableCellEditor.java b/src/main/java/net/rptools/maptool/client/swing/table/TextFieldEditorButtonTableCellEditor.java similarity index 98% rename from src/main/java/net/rptools/maptool/client/swing/TextFieldEditorButtonTableCellEditor.java rename to src/main/java/net/rptools/maptool/client/swing/table/TextFieldEditorButtonTableCellEditor.java index d344f37c03..554ca6e536 100644 --- a/src/main/java/net/rptools/maptool/client/swing/TextFieldEditorButtonTableCellEditor.java +++ b/src/main/java/net/rptools/maptool/client/swing/table/TextFieldEditorButtonTableCellEditor.java @@ -12,7 +12,7 @@ * and specifically the Affero license * text at . */ -package net.rptools.maptool.client.swing; +package net.rptools.maptool.client.swing.table; import java.awt.*; import javax.swing.*; diff --git a/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java b/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java new file mode 100644 index 0000000000..41c6b73c2e --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java @@ -0,0 +1,64 @@ +/* + * 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.swing.table; + +import com.formdev.flatlaf.ui.FlatUIUtils; +import java.awt.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import javax.swing.*; +import javax.swing.table.TableCellRenderer; +import net.rptools.maptool.client.AppConstants; +import net.rptools.maptool.client.AppPreferences; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.Theme; + +/* cell renderer for properties table */ +public class WordWrapCellRenderer extends RSyntaxTextArea implements TableCellRenderer { + private static final Logger log = LogManager.getLogger(); + + public WordWrapCellRenderer() { + setLineWrap(false); + setWrapStyleWord(true); + + /* Set the color style via Theme */ + try { + File themeFile = + new File(AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); + Theme theme = Theme.load(new FileInputStream(themeFile)); + theme.apply(this); + setFont(FlatUIUtils.nonUIResource(UIManager.getFont("monospaced.font"))); + revalidate(); + } catch (IOException e) { + log.error("Error while loading theme", e); + } + } + + public Component getTableCellRendererComponent( + JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + if (value == null) { + value = ""; + } + setText(value.toString()); + setSize(table.getColumnModel().getColumn(column).getWidth(), getPreferredSize().height); + if (table.getRowHeight(row) != getPreferredSize().height) { + table.setRowHeight(row, getPreferredSize().height); + } + return this; + } +} 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 2bfb126081..862c7a6711 100644 --- a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java @@ -1430,6 +1430,11 @@ && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet())) { if (property.isOwnerOnly() && !AppUtil.playerOwns(tokenUnderMouse)) { continue; } + if (!property + .getStatSheetViewPermission() + .hasPermission(MapTool.getPlayer(), tokenUnderMouse)) { + continue; + } timer.start(property.getName()); MapToolVariableResolver resolver = new MapToolVariableResolver(tokenUnderMouse); resolver.initialize(); @@ -1437,7 +1442,8 @@ && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet())) { Object propertyValue = tokenUnderMouse.getEvaluatedProperty(resolver, property.getName()); resolver.flush(); - if (propertyValue != null && propertyValue.toString().length() > 0) { + //noinspection ConstantValue + if (propertyValue != null && !propertyValue.toString().isEmpty()) { String propName = property.getShortName(); if (StringUtils.isEmpty(propName)) { propName = property.getName(); 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 c4a755d021..94e7edafb8 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 @@ -14,6 +14,7 @@ */ package net.rptools.maptool.client.ui.campaignproperties; +import com.jidesoft.grid.TableUtils; import java.awt.*; import java.awt.event.MouseEvent; import java.io.BufferedReader; @@ -26,13 +27,11 @@ import javax.swing.table.JTableHeader; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; - import net.rptools.CaseInsensitiveHashMap; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.swing.AbeillePanel; -import net.rptools.maptool.client.swing.MultiLineTableHeaderRenderer; -import net.rptools.maptool.client.swing.TextFieldEditorButtonTableCellEditor; +import net.rptools.maptool.client.swing.table.MultiLineTableHeaderRenderer; +import net.rptools.maptool.client.swing.table.TextFieldEditorButtonTableCellEditor; import net.rptools.maptool.client.ui.campaignproperties.TokenPropertiesTableModel.LargeEditableText; import net.rptools.maptool.client.ui.sheet.stats.StatSheetComboBoxRenderer; import net.rptools.maptool.client.ui.theme.Icons; @@ -60,7 +59,6 @@ public class TokenPropertiesManagementPanel extends AbeillePanel getStatSheetComboBox() { return (JComboBox) getComponent("statSheetComboBox"); } @@ -380,10 +377,7 @@ public void initPropertyTable() { LargeEditableText.class, new TextFieldEditorButtonTableCellEditor()); propertyTable.setDefaultEditor( - VariableType.class, new DefaultCellEditor(new JComboBox<>(VariableType.values()))); - - propertyTable.setDefaultEditor( - PermissionsScope.class, new DefaultCellEditor(new JComboBox<>(PermissionsScope.values()))); + Permissions.class, new DefaultCellEditor(new JComboBox<>(Permissions.values()))); propertyTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); propertyTable @@ -443,7 +437,6 @@ private void updateExistingTokenTypes(String oldName, String newName) { } public void initTypeList() { - getTokenTypeList() .addListSelectionListener( e -> { @@ -612,7 +605,7 @@ private List parseTokenProperties(String propertyText) String original, line; while ((original = reader.readLine()) != null) { line = original = original.trim(); - if (line.length() == 0) { + if (line.isEmpty()) { continue; } @@ -621,17 +614,17 @@ private List parseTokenProperties(String propertyText) // Prefix while (true) { if (line.startsWith("*")) { - property.setVisibilityPermission(PermissionsScope.ALL); + property.setStatSheetViewPermission(Permissions.ALL); line = line.substring(1); continue; } if (line.startsWith("@")) { - property.setVisibilityPermission(PermissionsScope.OWNER); + property.setStatSheetViewPermission(Permissions.OWNER); line = line.substring(1); continue; } if (line.startsWith("#")) { - property.setVisibilityPermission(PermissionsScope.GM); + property.setStatSheetViewPermission(Permissions.GM); line = line.substring(1); continue; } @@ -742,9 +735,9 @@ public String getToolTipText(MouseEvent event) { for (int i = 0; i < propertyTable.getColumnCount(); i++) { TableColumn column = propertyTable.getColumnModel().getColumn(i); - column.setHeaderRenderer(customHeaderRenderer); + column.setHeaderRenderer(customHeaderRenderer); } - + TableUtils.autoResizeColumn(propertyTable, 3); propertyTable.doLayout(); } diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanelView.form b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanelView.form index cba98c83da..e99355fe45 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanelView.form +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanelView.form @@ -153,7 +153,7 @@ - + @@ -163,36 +163,36 @@ - + - + - + - + - + - + @@ -211,92 +211,146 @@ - + - + - + - + - + - + - + - + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -441,7 +495,7 @@ - + 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 5676d03048..831b32b46a 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 @@ -21,9 +21,8 @@ import java.util.Map; import javax.swing.table.AbstractTableModel; import net.rptools.maptool.language.I18N; -import net.rptools.maptool.model.PermissionsScope; +import net.rptools.maptool.model.Permissions; import net.rptools.maptool.model.TokenProperty; -import net.rptools.maptool.model.VariableType; /** Table model for the token properties type table. */ public class TokenPropertiesTableModel extends AbstractTableModel { @@ -76,9 +75,9 @@ public Object getValueAt(int rowIndex, int columnIndex) { case 1 -> property.getShortName(); case 2 -> property.getDisplayName(); case 3 -> property.getDefaultValue(); - case 4 -> property.isPlayerEditable(); - case 5 -> property.getVariableType(); - case 6 -> property.getVisibilityPermission(); + case 4 -> property.getEditorViewPermission().equals(Permissions.OWNER); + case 5 -> property.getEditorEditPermission().equals(Permissions.OWNER); + case 6 -> property.getStatSheetViewPermission(); default -> null; }; } @@ -89,8 +88,8 @@ public String getColumnTooltipText(int column) { case 1 -> I18N.getText("campaignPropertiesTable.column.shortName.description"); case 2 -> I18N.getText("campaignPropertiesTable.column.displayName.description"); case 3 -> I18N.getText("campaignPropertiesTable.column.default.description"); - case 4 -> I18N.getText("campaignPropertiesTable.column.statSheet.playerEditable"); - case 5 -> I18N.getText("campaignPropertiesTable.column.valueType.description"); + case 4 -> I18N.getText("campaignPropertiesTable.column.playerViewable.description"); + case 5 -> I18N.getText("campaignPropertiesTable.column.playerEditable.description"); case 6 -> I18N.getText("campaignPropertiesTable.column.statSheet.description"); default -> ""; }; @@ -103,8 +102,8 @@ public String getColumnName(int column) { case 1 -> I18N.getText("campaignPropertiesTable.column.shortName"); case 2 -> I18N.getText("campaignPropertiesTable.column.displayName"); case 3 -> I18N.getText("campaignPropertiesTable.column.defaultValue"); - case 4 -> I18N.getText("campaignPropertiesTable.column.playerEditable"); - case 5 -> I18N.getText("campaignPropertiesTable.column.valueType"); + case 4 -> I18N.getText("campaignPropertiesTable.column.playerViewable"); + case 5 -> I18N.getText("campaignPropertiesTable.column.playerEditable"); case 6 -> I18N.getText("campaignPropertiesTable.column.statSheetVisibility"); default -> ""; }; @@ -115,18 +114,16 @@ public Class getColumnClass(int columnIndex) { return switch (columnIndex) { case 0, 1, 2 -> String.class; case 3 -> LargeEditableText.class; - case 4 -> Boolean.class; - case 5 -> VariableType.class; - case 6 -> PermissionsScope.class; + case 4, 5 -> Boolean.class; + case 6 -> Permissions.class; default -> null; }; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { - if(columnIndex == 5) { - List properties = tokenTypeMap.get(tokenType); - return properties.get(rowIndex).isPlayerEditable(); + if (columnIndex == 5) { + return (boolean) getValueAt(rowIndex, 4); } return true; } @@ -141,9 +138,9 @@ public void setValueAt(Object aValue, int rowIndex, int columnIndex) { case 1 -> tokenProperty.setShortName((String) aValue); case 2 -> tokenProperty.setDisplayName((String) aValue); case 3 -> tokenProperty.setDefaultValue((String) aValue); - case 4 -> tokenProperty.setPlayerEditable((boolean) aValue); - case 5 -> tokenProperty.setVariableType((VariableType) aValue); - case 6 -> tokenProperty.setVisibilityPermission((PermissionsScope) aValue); + case 4 -> tokenProperty.setEditorViewPermission((boolean) aValue); + case 5 -> tokenProperty.setEditorEditPermission((boolean) aValue); + case 6 -> tokenProperty.setStatSheetViewPermission((Permissions) aValue); } } diff --git a/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/TokenPropertiesEditorPanel.java b/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/TokenPropertiesEditorPanel.java index cec01c2211..c57099610c 100644 --- a/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/TokenPropertiesEditorPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/token/dialog/edit/TokenPropertiesEditorPanel.java @@ -14,12 +14,12 @@ */ package net.rptools.maptool.client.ui.token.dialog.edit; -import com.jidesoft.combobox.MultilineStringExComboBox; -import com.jidesoft.combobox.PopupPanel; +import static com.jidesoft.converter.ConverterContext.DEFAULT_CONTEXT; +import static com.jidesoft.swing.SearchableBar.*; + import com.jidesoft.converter.ConverterContext; import com.jidesoft.grid.*; -import com.jidesoft.plaf.basic.BasicExComboBoxUI; - +import com.jidesoft.swing.*; import java.awt.*; import java.awt.event.MouseEvent; import java.util.*; @@ -27,341 +27,257 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.swing.*; -import javax.swing.table.TableCellRenderer; - import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.swing.table.MTMultilineStringCellEditor; +import net.rptools.maptool.client.swing.table.WordWrapCellRenderer; import net.rptools.maptool.language.I18N; +import net.rptools.maptool.model.Permissions; import net.rptools.maptool.model.Token; import net.rptools.maptool.model.TokenProperty; -import net.rptools.maptool.model.VariableType; +import net.rptools.maptool.model.player.Player; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; -import org.fife.ui.rsyntaxtextarea.SyntaxConstants; -import org.fife.ui.rtextarea.RTextScrollPane; - -import static com.jidesoft.converter.ConverterContext.DEFAULT_CONTEXT; public class TokenPropertiesEditorPanel extends PropertyPane { - private static final Logger log = LogManager.getLogger(); - - private final EditTokenDialog editTokenDialog; - private final WordWrapCellRenderer wordWrapCellRenderer = new WordWrapCellRenderer(); - private final NumberCellRenderer numberCellRenderer = new NumberCellRenderer(); - private final NumberCellEditor numberCellEditor = new NumberCellEditor<>(); - private Token token; - private PropertyTableSearchable searchable; - - public TokenPropertiesEditorPanel(EditTokenDialog editTokenDialog) { - super(new PropertyTable() { - @Override - public String getToolTipText(MouseEvent event) { - String text = super.getToolTipText(event); - return text != null && text.length() > 100 ? text.substring(0, 100) + " ..." : text; - } - }, 1); - - this.editTokenDialog = editTokenDialog; - - setShowDescription(false); - - searchable = new PropertyTableSearchable(getPropertyTable()); - - getPropertyTable().setModel(new TokenPropertyTableModel()); - getPropertyTable().setFillsViewportHeight(true); - getPropertyTable().setName("propertiesTable"); - - - /* wrap button and functionality */ - JPanel buttonsAndPropertyTable = new JPanel(); - buttonsAndPropertyTable.setLayout(new BorderLayout()); - JCheckBox wrapToggle = new JCheckBox(I18N.getString("EditTokenDialog.msg.wrap")); - wrapToggle.addActionListener( - e -> { - wordWrapCellRenderer.setLineWrap(wrapToggle.isSelected()); - getPropertyTable().repaint(); - }); - buttonsAndPropertyTable.add(wrapToggle, BorderLayout.PAGE_END); - - - buttonsAndPropertyTable.add(this, BorderLayout.CENTER); - setMinimumSize(new Dimension(300, 200)); - setPreferredSize(new Dimension(-1, -1)); - setVisible(true); - } - - public void reset(Token token) { + private static final Logger log = LogManager.getLogger(); + private final EditTokenDialog editTokenDialog; + private final WordWrapCellRenderer wordWrapCellRenderer = new WordWrapCellRenderer(); + private static final String GM = I18N.getText("permission.displayName.gm"); + private static final String GM_SUFFIX = String.format(" (%s)", GM); + private Token token; + private TableSearchable searchable; + + public TokenPropertiesEditorPanel(EditTokenDialog editTokenDialog) { + super( + new PropertyTable() { + @Override + public String getToolTipText(MouseEvent event) { + String text = super.getToolTipText(event); + return text != null && text.length() > 100 ? text.substring(0, 100) + " ..." : text; + } + }, + 1); + + this.editTokenDialog = editTokenDialog; + + setShowDescription(false); + searchable = SearchableUtils.installSearchable(getPropertyTable()); + searchable.setSearchColumnIndices(new int[] {0, 1}); + + SearchableBar searchableBar = new SearchableBar(searchable, true); + searchableBar.setHighlightAll(true); + searchableBar.setVisibleButtons( + SHOW_NAVIGATION + | SHOW_HIGHLIGHTS + | SHOW_MATCHCASE + | SHOW_REPEATS + | SHOW_STATUS + | SHOW_WHOLE_WORDS); + + UIManager.getDefaults() + .entrySet() + .forEach( + entry -> { + if (entry.getKey() instanceof String key) { + if (key.toLowerCase().contains("table") + || key.contains("jide") + || key.startsWith("com.")) { + System.out.println(key + ":" + entry.getValue()); + } + } + }); + UIManager.getDefaults().put("Table.rowHeight", 24); + // move sort buttons to searchable toolbar + JideBoxLayout toolbarLayout = (JideBoxLayout) searchableBar.getLayout(); + toolbarLayout.addLayoutComponent(Box.createHorizontalStrut(2), JideBoxLayout.VARY); + searchableBar.add(new JSeparator(), 0); + for (Component c : getToolBar().getComponents()) { + searchableBar.add(c, 0); } - - - /** - * Updates the property table. - * - * @param propertyType the property type of the token (unused). - */ - protected void updatePropertiesTable(@Nullable Token token, final String propertyType) { - EventQueue.invokeLater( - () -> { - PropertyTable pp = getPropertyTable(); - List propertyTypeProperties = MapTool.getCampaign().getTokenPropertyList(propertyType); - pp.setModel( - new TokenPropertyTableModel(token, propertyType, propertyTypeProperties, wordWrapCellRenderer, numberCellEditor, numberCellRenderer)); - pp.expandAll(); - }); + add(searchableBar, BorderLayout.BEFORE_FIRST_LINE); + + getPropertyTable().setModel(new TokenPropertyTableModel()); + getPropertyTable().setFillsViewportHeight(true); + getPropertyTable().setDisableUneditableCells(false); + getPropertyTable().setName("propertiesTable"); + + /* wrap button and functionality */ + JPanel buttonsAndPropertyTable = new JPanel(); + buttonsAndPropertyTable.setLayout(new BorderLayout()); + JCheckBox wrapToggle = new JCheckBox(I18N.getString("EditTokenDialog.msg.wrap")); + wrapToggle.addActionListener( + e -> { + wordWrapCellRenderer.setLineWrap(wrapToggle.isSelected()); + getPropertyTable().repaint(); + }); + buttonsAndPropertyTable.add(wrapToggle, BorderLayout.PAGE_END); + + buttonsAndPropertyTable.add(this, BorderLayout.CENTER); + setMinimumSize(new Dimension(300, 200)); + setPreferredSize(new Dimension(-1, -1)); + setVisible(true); + } + + public void reset(Token token) {} + + /** + * Updates the property table. + * + * @param propertyType the property type of the token (unused). + */ + protected void updatePropertiesTable(@Nullable Token token, final String propertyType) { + EventQueue.invokeLater( + () -> { + PropertyTable pp = getPropertyTable(); + List propertyTypeProperties = + MapTool.getCampaign().getTokenPropertyList(propertyType); + pp.setModel( + new TokenPropertyTableModel( + token, propertyType, propertyTypeProperties, wordWrapCellRenderer)); + pp.expandAll(); + }); + } + + protected static class TokenPropertyTableModel + extends PropertyTableModel + implements NavigableModel { + + private final Map propertyMap; + private final ArrayList gridProperties = new ArrayList<>(); + private Set otherPropertyNames; + + public TokenPropertyTableModel() { + otherPropertyNames = Set.of(); + propertyMap = Map.of(); } - protected static class TokenPropertyTableModel - extends PropertyTableModel - implements NavigableModel { - final String gm = String.format(" (%s)", I18N.getText("permissionsScope.displayName.gm")); - private final java.util.List propertyList; - private final Map propertyMap; - - public TokenPropertyTableModel() { - propertyList = java.util.List.of(); - propertyMap = Map.of(); - } - - public TokenPropertyTableModel( - @Nullable Token model, - String propertyTypeName, - List propertyList, - WordWrapCellRenderer wordWrapCellRenderer, - NumberCellEditor numberCellEditor, - NumberCellRenderer numberCellRenderer) { - this.propertyList = propertyList; - - Set typePropertyNames = propertyList.stream().map(TokenProperty::getName).collect(Collectors.toSet()); - Set otherPropertyNames = Set.of(); - if (model != null) { - otherPropertyNames = model.getPropertyNamesRaw(); - otherPropertyNames = otherPropertyNames.stream().filter(name -> !typePropertyNames.stream().toList().contains(name)).collect(Collectors.toSet()); - } - - this.propertyMap = new HashMap<>(); - - ArrayList gridProperties = new ArrayList<>(); - for(TokenProperty tp : propertyList){ - String value = null; - if(model != null){ - value = (String) model.getProperty(tp.getName()); - } - this.propertyMap.put(tp.getName(), value == null && tp.hasDefaultValue() ? tp.getDefaultValue() : value); - - TableTokenProperty gridProperty = new TableTokenProperty(tp, propertyTypeName); - if(tp.getVariableType().equals(VariableType.NUMBER)){ - gridProperty.setTableCellRenderer(numberCellRenderer); - gridProperty.setCellEditor(numberCellEditor); - } else { - gridProperty.setTableCellRenderer(wordWrapCellRenderer); - gridProperty.setCellEditor(new MTMultilineStringCellEditor()); - } - gridProperties.add(gridProperty); - } - for (String propName : otherPropertyNames) { - this.propertyMap.put(propName, (String) model.getProperty(propName)); - - TableTokenProperty gridProperty = new TableTokenProperty(propName, propertyTypeName, gm); - gridProperty.setTableCellRenderer(wordWrapCellRenderer); - gridProperty.setCellEditor(new MTMultilineStringCellEditor()); - gridProperties.add(gridProperty); - } - - setOriginalProperties(gridProperties); - } - - public void applyTo(Token token) { - for (TokenProperty property : propertyList) { - String value = propertyMap.get(property.getName()); - if (property.getDefaultValue() != null && property.getDefaultValue().equals(value)) { - token.setProperty(property.getName(), null); // Clear original value - continue; - } - token.setProperty(property.getName(), value); - } - } - - @Override - public boolean isNavigableAt(int rowIndex, int columnIndex) { - /* make the property name column non-navigable so that tab takes you directly to the next property value cell. */ - return (columnIndex != 0); - } - - @Override - public boolean isNavigationOn() { - return true; - } - - class TableTokenProperty extends Property { - public TableTokenProperty(TokenProperty tokenProperty, String propertyType) { - this(tokenProperty.getName(), - tokenProperty.getDisplayName(), - tokenProperty.getVariableType() == null ? VariableType.UNDEFINED.getClass() : tokenProperty.getVariableType().getKlass(), - tokenProperty.isPlayerEditable() ? propertyType : propertyType + gm, - DEFAULT_CONTEXT, - null); - } - - public TableTokenProperty(String propertyName, String propertyType, String category) { - this(propertyName, propertyName, String.class, propertyType, DEFAULT_CONTEXT, null); - } - - public TableTokenProperty(String name, String displayName, Class klass, String category, ConverterContext converterContext, List children) { - super(name, "", klass, category, converterContext, children); - setDisplayName(displayName); - } - - @Override - public Object getValue() { - return propertyMap.get(getName()); - } - - @Override - public void setValue(Object value) { - propertyMap.put(getName(), (String) value); - } - - @Override - public boolean hasValue() { - return propertyMap.get(getName()) != null; - } + public TokenPropertyTableModel( + @Nullable Token model, + String propertyTypeName, + List propertyList, + WordWrapCellRenderer wordWrapCellRenderer) { + gridProperties.clear(); + + Player player = MapTool.getPlayer(); + + Set typePropertyNames = + propertyList.stream().map(TokenProperty::getName).collect(Collectors.toSet()); + Set otherPropertyNames = Set.of(); + if (model != null) { + otherPropertyNames = model.getPropertyNamesRaw(); + otherPropertyNames = + otherPropertyNames.stream() + .filter(name -> !typePropertyNames.stream().toList().contains(name)) + .collect(Collectors.toSet()); + } + + this.propertyMap = new HashMap<>(); + + for (TokenProperty tp : propertyList) { + String value = null; + if (model != null) { + value = (String) model.getProperty(tp.getName()); } + this.propertyMap.put( + tp.getName(), value == null && tp.hasDefaultValue() ? tp.getDefaultValue() : value); + + TableTokenProperty gridProperty = new TableTokenProperty(tp, propertyTypeName); + gridProperty.setTableCellRenderer(wordWrapCellRenderer); + gridProperty.setEditable(tp.getEditorEditPermission().hasPermission(player, model)); + gridProperty.setCellEditor(new MTMultilineStringCellEditor()); + gridProperties.add(gridProperty); + } + for (String propName : otherPropertyNames) { + this.propertyMap.put(propName, (String) model.getProperty(propName)); + + TableTokenProperty gridProperty = new TableTokenProperty(propName, "", GM); + gridProperty.setTableCellRenderer(wordWrapCellRenderer); + gridProperty.setCellEditor(new MTMultilineStringCellEditor()); + gridProperties.add(gridProperty); + } + + setOriginalProperties(gridProperties); } - /* needed to change the popup for properties */ - private static class MTMultilineStringExComboBox extends MultilineStringExComboBox { - - final ResourceBundle a = ResourceBundle.getBundle("com.jidesoft.combobox.combobox"); - - public ResourceBundle getResourceBundle(Locale paramLocale) { - return ResourceBundle.getBundle("com.jidesoft.combobox.combobox", paramLocale); - } - - public PopupPanel createPopupComponent() { - MTMultilineStringPopupPanel pp = - new MTMultilineStringPopupPanel( - getResourceBundle(Locale.getDefault()).getString("ComboBox.multilineStringTitle")); - return pp; + public void applyTo(Token token) { + for (TableTokenProperty tableProp : gridProperties) { + String value = propertyMap.get(tableProp.getName()); + if (tableProp.hasTokenProperty()) { + TokenProperty tp = tableProp.getTokenProperty(); + if (tp.getDefaultValue() != null && tp.getDefaultValue().equals(value)) { + token.setProperty(tableProp.getName(), null); // Clear original value + continue; + } } + token.setProperty(tableProp.getName(), value); + } } - /* the cell editor for property popups */ - private static class MTMultilineStringCellEditor extends MultilineStringCellEditor { - - protected MTMultilineStringExComboBox createMultilineStringComboBox() { - MTMultilineStringExComboBox localMultilineStringExComboBox = - new MTMultilineStringExComboBox(); - localMultilineStringExComboBox.setEditable(true); - localMultilineStringExComboBox.setUI(new BasicExComboBoxUI()); - return localMultilineStringExComboBox; - } + @Override + public boolean isNavigableAt(int rowIndex, int columnIndex) { + /* make the property name column non-navigable so that tab takes you directly to the next property value cell. */ + return (columnIndex != 0); } - /* cell renderer for properties table */ - protected static class WordWrapCellRenderer extends RSyntaxTextArea implements TableCellRenderer { - - WordWrapCellRenderer() { - setLineWrap(false); - setWrapStyleWord(true); -// -// /* Set the color style via Theme */ -// try { -// File themeFile = -// new File( -// AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); -// Theme theme = Theme.load(new FileInputStream(themeFile)); -// theme.apply(this); -// -// revalidate(); -// } catch (IOException e) { -// log.error("Error while loading theme", e); -// } - } - - public Component getTableCellRendererComponent( - JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - if (value == null) { - value = ""; - } - setText(value.toString()); - setSize(table.getColumnModel().getColumn(column).getWidth(), getPreferredSize().height); - if (table.getRowHeight(row) != getPreferredSize().height) { - table.setRowHeight(row, getPreferredSize().height); - } - return this; - } + @Override + public boolean isNavigationOn() { + return true; } - /* the property popup table */ - private static class MTMultilineStringPopupPanel extends PopupPanel { - - private RSyntaxTextArea j = createTextArea(); - - public MTMultilineStringPopupPanel() { - this(""); - } - - public MTMultilineStringPopupPanel(String paramString) { - this.setResizable(true); -// /* Set the color style via Theme */ -// try { -// File themeFile = -// new File( -// AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); -// Theme theme = Theme.load(new FileInputStream(themeFile)); -// theme.apply(j); -// -// j.revalidate(); -// } catch (IOException e) { -// log.error("Error while loading multiline property editor theme", e); -// } - JScrollPane localJScrollPane = new RTextScrollPane(j); - localJScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); - localJScrollPane.setAutoscrolls(true); - localJScrollPane.setPreferredSize(new Dimension(300, 200)); - setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 5)); - setLayout(new BorderLayout()); - setTitle(paramString); - add(localJScrollPane, "Center"); - setDefaultFocusComponent(j); - j.setLineWrap(false); - JCheckBox wrapToggle = new JCheckBox(I18N.getString("EditTokenDialog.msg.wrap")); - wrapToggle.addActionListener(e -> j.setLineWrap(!j.getLineWrap())); - - DefaultComboBoxModel syntaxListModel = new DefaultComboBoxModel(); - syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_NONE); - syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_JSON); - syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE); - syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_HTML); - syntaxListModel.addElement(SyntaxConstants.SYNTAX_STYLE_XML); - JComboBox syntaxComboBox = new JComboBox(syntaxListModel); - syntaxComboBox.addActionListener( - e -> j.setSyntaxEditingStyle(syntaxComboBox.getSelectedItem().toString())); - - add(syntaxComboBox, BorderLayout.BEFORE_FIRST_LINE); - add(wrapToggle, BorderLayout.AFTER_LAST_LINE); - } - - public Object getSelectedObject() { - return j.getText(); - } - - public void setSelectedObject(Object paramObject) { - if (paramObject != null) { - j.setText(paramObject.toString()); - } else { - j.setText(""); - } - } - - protected RSyntaxTextArea createTextArea() { - RSyntaxTextArea textArea = new RSyntaxTextArea(); - textArea.setUseFocusableTips(false); - textArea.setAnimateBracketMatching(true); - textArea.setBracketMatchingEnabled(true); - textArea.setLineWrap(false); - textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE); - return textArea; + class TableTokenProperty extends Property { + TokenProperty tokenProperty; + + public TableTokenProperty(TokenProperty tokenProperty, String propertyType) { + this( + tokenProperty.getName(), + tokenProperty.getDisplayName(), + String.class, + propertyType, + DEFAULT_CONTEXT, + null); + this.tokenProperty = tokenProperty; + if (tokenProperty.getEditorViewPermission().equals(Permissions.GM)) { + this.setCategory(this.getCategory() + GM_SUFFIX); } + } + + public TableTokenProperty(String propertyName, String propertyType, String category) { + this(propertyName, propertyName, String.class, propertyType, DEFAULT_CONTEXT, null); + } + + public TableTokenProperty( + String name, + String displayName, + Class klass, + String category, + ConverterContext converterContext, + List children) { + super(name, "", klass, category, converterContext, children); + setDisplayName(displayName); + } + + public boolean hasTokenProperty() { + return tokenProperty != null; + } + + public TokenProperty getTokenProperty() { + return tokenProperty; + } + + @Override + public Object getValue() { + return propertyMap.get(getName()); + } + + @Override + public void setValue(Object value) { + propertyMap.put(getName(), (String) value); + } + + @Override + public boolean hasValue() { + return propertyMap.get(getName()) != null; + } } + } } diff --git a/src/main/java/net/rptools/maptool/model/CampaignProperties.java b/src/main/java/net/rptools/maptool/model/CampaignProperties.java index 0750a548ae..4a49600d27 100644 --- a/src/main/java/net/rptools/maptool/model/CampaignProperties.java +++ b/src/main/java/net/rptools/maptool/model/CampaignProperties.java @@ -56,7 +56,6 @@ import net.rptools.maptool.server.proto.HaloListDto; import net.rptools.maptool.server.proto.LightSourceListDto; import net.rptools.maptool.server.proto.TokenPropertyListDto; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.CaseUtils; public class CampaignProperties implements Serializable { @@ -145,7 +144,8 @@ public CampaignProperties() {} public CampaignProperties(CampaignProperties properties) { for (Entry> entry : properties.tokenTypeMap.entrySet()) { - List typeProperties = new ArrayList<>(properties.tokenTypeMap.get(entry.getKey())); + List typeProperties = + new ArrayList<>(properties.tokenTypeMap.get(entry.getKey())); tokenTypeMap.put(entry.getKey(), typeProperties); } @@ -594,23 +594,36 @@ private void initTokenTypeMap() { } List list = new ArrayList<>(); - final String[] basicPropNames = new String[]{"strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma", "hp", "ac", "defense", "movement", "elevation", "description"}; - for(String propName : basicPropNames){ - PermissionsScope visibility = switch (propName){ - case "hp", "ac" -> PermissionsScope.OWNER; - case "elevation", "description" -> PermissionsScope.ALL; - default -> PermissionsScope.NONE; - }; + final String[] basicPropNames = + new String[] { + "strength", + "dexterity", + "constitution", + "intelligence", + "wisdom", + "charisma", + "hp", + "ac", + "defense", + "movement", + "elevation", + "description" + }; + for (String propName : basicPropNames) { + Permissions visibility = + switch (propName) { + case "hp", "ac" -> Permissions.OWNER; + case "elevation", "description" -> Permissions.ALL; + default -> Permissions.NONE; + }; String displayName = I18N.getText(PROP_PREFIX + propName); - TokenProperty tp = new TokenProperty( + TokenProperty tp = + new TokenProperty( CaseUtils.toCamelCase(displayName, false), I18N.getText(SHORT_PROP_PREFIX + propName), displayName, - propName.equals("description") ? VariableType.STRING : VariableType.NUMBER, - visibility - ); - + visibility); list.add(tp); } diff --git a/src/main/java/net/rptools/maptool/model/TokenProperty.java b/src/main/java/net/rptools/maptool/model/TokenProperty.java index 342135922f..fa51529238 100644 --- a/src/main/java/net/rptools/maptool/model/TokenProperty.java +++ b/src/main/java/net/rptools/maptool/model/TokenProperty.java @@ -15,343 +15,343 @@ package net.rptools.maptool.model; import com.google.protobuf.StringValue; - import java.io.Serializable; import java.util.Objects; - import net.rptools.maptool.server.proto.TokenPropertyDto; public class TokenProperty implements DisplayNames, Serializable { - private String name; - private String shortName; - private String displayName; - private boolean playerEditable = true; - private PermissionsScope visibilityPermission = PermissionsScope.NONE; - private VariableType variableType = VariableType.UNDEFINED; - private String defaultValue = ""; - - public TokenProperty() { - // For serialization - } - - public TokenProperty(String name) { - this(name, null, (String) null); - } - - public TokenProperty(String name, String shortName) { - this(name, shortName, (String) null); - } - public TokenProperty(String name, String shortName, String displayName) { - this.name = name; - this.shortName = shortName; - this.displayName = displayName; - } - public TokenProperty(String name, VariableType variableType) { - this(name, null, null, true, variableType, PermissionsScope.NONE, null); - } - - public TokenProperty(String name, boolean playerEditable) { - this(name, null, null, playerEditable, null, PermissionsScope.NONE, null); - } - - public TokenProperty(String name, PermissionsScope visibilityPermission) { - this(name, null, null, true, null, visibilityPermission, null); - } - - public TokenProperty(String name, boolean playerEditable, VariableType variableType) { - this(name, null, null, playerEditable, variableType, PermissionsScope.NONE, null); - } - - public TokenProperty(String name, String shortName, VariableType variableType) { - this(name, shortName, null, true, variableType, PermissionsScope.NONE, null); - } - - public TokenProperty(String name, String shortName, boolean playerEditable) { - this(name, shortName, null, playerEditable, null, PermissionsScope.NONE, null); - } - - public TokenProperty(String name, String shortName, boolean playerEditable, VariableType variableType) { - this(name, shortName, null, playerEditable, variableType, PermissionsScope.NONE, null); - } - - - public TokenProperty(String name, String shortName, String displayName, VariableType variableType) { - this(name, shortName, displayName, true, variableType, PermissionsScope.NONE, null); - } - - public TokenProperty(String name, String shortName, String displayName, boolean playerEditable) { - this(name, shortName, displayName, playerEditable, null, PermissionsScope.NONE, null); - } - - public TokenProperty(String name, String shortName, String displayName, boolean playerEditable, VariableType variableType) { - this(name, shortName, displayName, playerEditable, variableType, PermissionsScope.NONE, null); - } - - public TokenProperty(String name, VariableType variableType, PermissionsScope visibilityPermission) { - this(name, null, null, true, variableType, visibilityPermission, null); - } - - public TokenProperty(String name, boolean playerEditable, PermissionsScope visibilityPermission) { - this(name, null, null, playerEditable, null, visibilityPermission, null); - } - - public TokenProperty(String name, boolean playerEditable, VariableType variableType, PermissionsScope visibilityPermission) { - this(name, null, null, playerEditable, variableType, visibilityPermission, null); - } - - public TokenProperty(String name, String shortName, PermissionsScope visibilityPermission) { - this(name, shortName, null, true, null, visibilityPermission, null); - } - - public TokenProperty(String name, String shortName, VariableType variableType, PermissionsScope visibilityPermission) { - this(name, shortName, null, true, variableType, visibilityPermission, null); - } - - public TokenProperty( - String name, - String shortName, - boolean playerEditable, - PermissionsScope visibilityPermission) { - this(name, shortName, null, playerEditable, null, visibilityPermission, null); - } - - public TokenProperty( - String name, - String shortName, - boolean playerEditable, - VariableType variableType, - PermissionsScope visibilityPermission) { - this(name, shortName, null, playerEditable, variableType, visibilityPermission, null); - } - - public TokenProperty( - String name, String shortName, PermissionsScope visibilityPermission, String defaultValue) { - this(name, shortName, null, true, null, visibilityPermission, defaultValue); - } - - public TokenProperty( - String name, String shortName, VariableType variableType, PermissionsScope visibilityPermission, String defaultValue) { - this(name, shortName, null, true, variableType, visibilityPermission, defaultValue); - } - - public TokenProperty( - String name, - String shortName, - boolean playerEditable, - PermissionsScope visibilityPermission, - String defaultValue) { - this(name, shortName, null, playerEditable, null, visibilityPermission, defaultValue); - } - - public TokenProperty( - String name, - String shortName, - boolean playerEditable, - VariableType variableType, - PermissionsScope visibilityPermission, - String defaultValue) { - this(name, shortName, null, playerEditable, variableType, visibilityPermission, defaultValue); - } - - public TokenProperty( - String name, String shortName, String displayName, PermissionsScope visibilityPermission) { - this(name, shortName, displayName, true, null, visibilityPermission, null); - } - - public TokenProperty( - String name, String shortName, String displayName, VariableType variableType, PermissionsScope visibilityPermission) { - this(name, shortName, displayName, true, variableType, visibilityPermission, null); - } - - public TokenProperty( - String name, - String shortName, - String displayName, - boolean playerEditable, - PermissionsScope visibilityPermission) { - this(name, shortName, displayName, playerEditable, null, visibilityPermission, null); - } - - public TokenProperty( - String name, - String shortName, - String displayName, - boolean playerEditable, - VariableType variableType, - PermissionsScope visibilityPermission) { - this(name, shortName, displayName, playerEditable, variableType, visibilityPermission, null); - } - - public TokenProperty( - String name, - String shortName, - String displayName, - boolean playerEditable, - VariableType variableType, - PermissionsScope visibilityPermission, - String defaultValue) { - this.name = name; - this.shortName = shortName; - this.displayName = displayName; - this.playerEditable = playerEditable; - if (variableType != null) { - this.variableType = variableType; - } - if (visibilityPermission != null) { - this.visibilityPermission = visibilityPermission; - } - this.defaultValue = defaultValue; - } - - /** - * Creates a new TokenProperty that's a copy of another. - * - * @param prop the property to copy the values from. - */ - public TokenProperty(TokenProperty prop) { - this.name = prop.name; - this.shortName = prop.shortName; - this.displayName = prop.displayName; - this.playerEditable = prop.playerEditable; - this.variableType = prop.variableType; - this.visibilityPermission = prop.visibilityPermission; - this.defaultValue = prop.defaultValue; - } - - public static TokenProperty fromDto(TokenPropertyDto dto) { - var prop = new TokenProperty(); - prop.name = dto.getName(); - prop.shortName = dto.hasShortName() ? dto.getShortName().getValue() : null; - - prop.playerEditable = !dto.hasPlayerEditable() || dto.getPlayerEditable(); - prop.variableType = !dto.hasVariableType() ? VariableType.UNDEFINED : VariableType.valueOf(dto.getVariableType()); - - if (dto.hasPermissions()) { - // the new permissions - prop.visibilityPermission = PermissionsScope.valueOf(dto.getPermissions()); - } else if (dto.hasHighPriority() && dto.getHighPriority()) { - // the old permissions - if (dto.hasGmOnly() && dto.getGmOnly()) { - prop.visibilityPermission = PermissionsScope.GM; - } else if (dto.hasOwnerOnly() && dto.getOwnerOnly()) { - prop.visibilityPermission = PermissionsScope.OWNER; - } else { - prop.visibilityPermission = PermissionsScope.ALLIED_ONLY; - } - } - - prop.defaultValue = dto.hasDefaultValue() ? dto.getDefaultValue().getValue() : null; - prop.displayName = dto.hasDisplayName() ? dto.getDisplayName().getValue() : null; - return prop; - } - - public boolean isShowOnStatSheet() { - return visibilityPermission != null - && !visibilityPermission.equals(PermissionsScope.NONE); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public boolean hasDisplayName() { - return displayName != null && !displayName.isBlank(); - } - - public String getDisplayName() { - return displayName; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - public boolean hasShortName() { - return shortName != null && !shortName.isBlank(); - } - - public String getShortName() { - return shortName; - } - - public void setShortName(String shortName) { - this.shortName = shortName; - } - - public boolean isPlayerEditable() { - return playerEditable; - } - - public void setPlayerEditable(boolean playerEditable) { - this.playerEditable = playerEditable; - } - - public PermissionsScope getVisibilityPermission() { - if(visibilityPermission == null){ - visibilityPermission = PermissionsScope.NONE; - } - return visibilityPermission; - } - - public void setVisibilityPermission(PermissionsScope visibilityPermission) { - this.visibilityPermission = visibilityPermission; - } - - public boolean isGMOnly() { - return visibilityPermission.equals(PermissionsScope.GM); - } - - public boolean isOwnerOnly() { - return visibilityPermission.equals(PermissionsScope.OWNER); - } - - public boolean isAllyOnly() { - return visibilityPermission.equals(PermissionsScope.ALLIED_ONLY); - } - - public boolean hasDefaultValue() { - return defaultValue != null && !defaultValue.isBlank(); - } - - public String getDefaultValue() { - return this.defaultValue; - } - - public void setDefaultValue(String defaultValue) { - this.defaultValue = defaultValue; - } - - - public VariableType getVariableType() { - if(variableType == null){ - variableType = VariableType.UNDEFINED; - } - return variableType; - } - - public void setVariableType(VariableType variableType) { - this.variableType = variableType; - } - - public TokenPropertyDto toDto() { - var dto = TokenPropertyDto.newBuilder(); - dto.setName(name); - dto.setPlayerEditable(playerEditable); - dto.setVariableType(Objects.requireNonNullElse(variableType, VariableType.UNDEFINED).name()); - dto.setPermissions(Objects.requireNonNullElse(visibilityPermission, PermissionsScope.NONE).name()); - if (hasShortName()) { - dto.setShortName(StringValue.of(shortName)); - } - if (hasDisplayName()) { - dto.setDisplayName(StringValue.of(displayName)); - } - if (hasDefaultValue()) { - dto.setDefaultValue(StringValue.of(defaultValue)); - } - return dto.build(); - } + private String name; + private String shortName; + private String displayName; + private String defaultValue = ""; + + // old stat-sheet permissions + @Deprecated private boolean highPriority; // showOnStatSheet; so that 1.3b28 files load in 1.3b29 + @Deprecated private boolean ownerOnly; + @Deprecated private boolean gmOnly; + + // new permissions + private Permissions editorViewPermission = Permissions.OWNER; + private Permissions editorEditPermission = Permissions.OWNER; + private Permissions statSheetViewPermission = Permissions.NONE; + + public TokenProperty() { + // For serialization + } + + public TokenProperty(String name) { + this(name, null, (String) null); + } + + public TokenProperty(String name, String shortName) { + this(name, shortName, (String) null); + } + + public TokenProperty(String name, String shortName, String displayName) { + this.name = name; + this.shortName = shortName; + this.displayName = displayName; + } + + public TokenProperty(String name, boolean playerEditable) { + this(name, null, null, playerEditable, Permissions.NONE, null); + } + + public TokenProperty(String name, Permissions statSheetViewPermission) { + this(name, null, null, true, statSheetViewPermission, null); + } + + public TokenProperty(String name, String shortName, boolean playerEditable) { + this(name, shortName, null, playerEditable, Permissions.NONE, null); + } + + public TokenProperty(String name, String shortName, String displayName, boolean playerEditable) { + this(name, shortName, displayName, playerEditable, Permissions.NONE, null); + } + + public TokenProperty(String name, boolean playerEditable, Permissions statSheetViewPermission) { + this(name, null, null, playerEditable, statSheetViewPermission, null); + } + + public TokenProperty(String name, String shortName, Permissions statSheetViewPermission) { + this(name, shortName, null, true, statSheetViewPermission, null); + } + + public TokenProperty( + String name, String shortName, boolean playerEditable, Permissions statSheetViewPermission) { + this(name, shortName, null, playerEditable, statSheetViewPermission, null); + } + + public TokenProperty( + String name, String shortName, Permissions statSheetViewPermission, String defaultValue) { + this(name, shortName, null, true, statSheetViewPermission, defaultValue); + } + + public TokenProperty( + String name, + String shortName, + boolean playerEditable, + Permissions statSheetViewPermission, + String defaultValue) { + this(name, shortName, null, playerEditable, statSheetViewPermission, defaultValue); + } + + public TokenProperty( + String name, String shortName, String displayName, Permissions statSheetViewPermission) { + this(name, shortName, displayName, true, statSheetViewPermission, null); + } + + public TokenProperty( + String name, + String shortName, + String displayName, + boolean playerEditable, + Permissions statSheetViewPermission) { + this(name, shortName, displayName, playerEditable, statSheetViewPermission, null); + } + + public TokenProperty( + String name, + String shortName, + String displayName, + boolean playerEditable, + Permissions statSheetViewPermission, + String defaultValue) { + this( + name, + shortName, + displayName, + playerEditable, + playerEditable, + statSheetViewPermission, + defaultValue); + } + + public TokenProperty( + String name, + String shortName, + boolean highPriority, + boolean isOwnerOnly, + boolean isGMOnly, + String defaultValue) { + this.name = name; + this.shortName = shortName; + this.highPriority = highPriority; + this.ownerOnly = isOwnerOnly; + this.gmOnly = isGMOnly; + this.defaultValue = defaultValue; + } + + public TokenProperty( + String name, + String shortName, + String displayName, + boolean playerViewable, + boolean playerEditable, + Permissions statSheetViewPermission, + String defaultValue) { + this.name = name; + this.shortName = shortName; + this.displayName = displayName; + this.editorEditPermission = playerEditable ? Permissions.OWNER : Permissions.GM; + this.editorViewPermission = playerViewable ? Permissions.OWNER : Permissions.GM; + if (statSheetViewPermission != null) { + this.statSheetViewPermission = statSheetViewPermission; + } + this.defaultValue = defaultValue; + } + + /** + * Creates a new TokenProperty that's a copy of another. + * + * @param prop the property to copy the values from. + */ + @SuppressWarnings("CopyConstructorMissesField") + public TokenProperty(TokenProperty prop) { + this.name = prop.getName(); + this.shortName = prop.getShortName(); + this.displayName = prop.getDisplayName(); + this.defaultValue = prop.getDefaultValue(); + + if (prop.highPriority) { + if (prop.ownerOnly) { + setStatSheetViewPermission(Permissions.OWNER); + } else if (prop.gmOnly) { + setStatSheetViewPermission(Permissions.GM); + } else { + setStatSheetViewPermission(Permissions.ALL); + } + } else if (prop.hasStatSheetViewPermission()) { + setStatSheetViewPermission(prop.getStatSheetViewPermission()); + } + // because these getters substitute nulls for defaults + setEditorEditPermission(prop.getEditorEditPermission()); + setEditorViewPermission(prop.getEditorViewPermission()); + } + + public boolean isShowOnStatSheet() { + return statSheetViewPermission != null && !statSheetViewPermission.equals(Permissions.NONE); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean hasDisplayName() { + return displayName != null && !displayName.isBlank(); + } + + public boolean hasShortName() { + return shortName != null && !shortName.isBlank(); + } + + public boolean hasDefaultValue() { + return defaultValue != null && !defaultValue.isBlank(); + } + + public boolean hasEditorViewPermission() { + return editorViewPermission != null; + } + + public boolean hasEditorEditPermission() { + return editorEditPermission != null; + } + + public boolean hasStatSheetViewPermission() { + return statSheetViewPermission != null; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getShortName() { + return shortName; + } + + public void setShortName(String shortName) { + this.shortName = shortName; + } + + public Permissions getStatSheetViewPermission() { + if (!hasStatSheetViewPermission()) { + statSheetViewPermission = Permissions.NONE; + } + return statSheetViewPermission; + } + + public void setStatSheetViewPermission(Permissions statSheetViewPermission) { + this.statSheetViewPermission = statSheetViewPermission; + } + + public boolean isGMOnly() { + return statSheetViewPermission.equals(Permissions.GM); + } + + public boolean isOwnerOnly() { + return statSheetViewPermission.equals(Permissions.OWNER); + } + + public String getDefaultValue() { + return this.defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public Permissions getEditorViewPermission() { + if (!hasEditorViewPermission()) { + this.editorViewPermission = Permissions.OWNER; + } + return editorViewPermission; + } + + public void setEditorViewPermission(Permissions editorViewPermission) { + this.editorViewPermission = editorViewPermission; + } + + public void setEditorViewPermission(boolean value) { + if (value) { + setEditorViewPermission(Permissions.OWNER); + } else { + setEditorViewPermission(Permissions.GM); + setEditorEditPermission(Permissions.GM); + } + } + + public Permissions getEditorEditPermission() { + if (!hasEditorEditPermission()) { + this.editorEditPermission = Permissions.OWNER; + } + return editorEditPermission; + } + + public void setEditorEditPermission(boolean value) { + setEditorEditPermission(value ? Permissions.OWNER : Permissions.GM); + } + + public void setEditorEditPermission(Permissions editorEditPermission) { + this.editorEditPermission = editorEditPermission; + } + + public static TokenProperty fromDto(TokenPropertyDto dto) { + var prop = new TokenProperty(); + prop.name = dto.getName(); + prop.shortName = dto.hasShortName() ? dto.getShortName().getValue() : null; + + prop.editorEditPermission = + dto.hasEditorEditPermission() + ? Permissions.valueOf(dto.getEditorEditPermission()) + : Permissions.OWNER; + prop.editorViewPermission = + dto.hasEditorViewPermission() + ? Permissions.valueOf(dto.getEditorViewPermission()) + : Permissions.OWNER; + prop.statSheetViewPermission = Permissions.valueOf(dto.getStatSheetViewPermission()); + + prop.defaultValue = dto.hasDefaultValue() ? dto.getDefaultValue().getValue() : null; + prop.displayName = dto.hasDisplayName() ? dto.getDisplayName().getValue() : null; + return prop; + } + + public TokenPropertyDto toDto() { + var dto = TokenPropertyDto.newBuilder(); + dto.setName(name); + dto.setEditorEditPermission( + Objects.requireNonNullElse(editorEditPermission, Permissions.OWNER).name()); + dto.setEditorViewPermission( + Objects.requireNonNullElse(editorViewPermission, Permissions.OWNER).name()); + // for campaigns pre 1.19 + if (highPriority || ownerOnly || gmOnly) { + if (ownerOnly) { + dto.setStatSheetViewPermission(Permissions.OWNER.name()); + } else if (gmOnly) { + dto.setStatSheetViewPermission(Permissions.GM.name()); + } else { + dto.setStatSheetViewPermission(Permissions.ALL.name()); + } + } else { + dto.setStatSheetViewPermission( + Objects.requireNonNullElse(statSheetViewPermission, Permissions.NONE).name()); + } + if (hasShortName()) { + dto.setShortName(StringValue.of(shortName)); + } + if (hasDisplayName()) { + dto.setDisplayName(StringValue.of(displayName)); + } + if (hasDefaultValue()) { + dto.setDefaultValue(StringValue.of(defaultValue)); + } + return dto.build(); + } } diff --git a/src/main/java/net/rptools/maptool/model/VariableType.java b/src/main/java/net/rptools/maptool/model/VariableType.java deleted file mode 100644 index 2f0c09c672..0000000000 --- a/src/main/java/net/rptools/maptool/model/VariableType.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.rptools.maptool.model; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import net.rptools.maptool.language.I18N; - -public enum VariableType { - UNDEFINED("variable.type.undefined", String.class), - JSON("variable.type.json", JsonElement.class), - JSON_ARRAY("variable.type.jsonArray", JsonArray.class), - JSON_OBJECT("variable.type.jsonObject", JsonObject.class), - NUMBER("variable.type.number", Number.class), - STRING("variable.type.string", String.class), - STRING_LIST("variable.type.stringList", String.class), - STRING_PROP_LIST("variable.type.stringPropList", String.class), - ; - final String displayName; - final Class klass; - VariableType(String i18nKey, Class klass){ - this.displayName = I18N.getText(i18nKey); - this.klass = klass; - } - - public Class getKlass() { - return klass; - } - - @Override - public String toString() { - return displayName; - } -} 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 b400c749b3..30f5249f67 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 @@ -32,9 +32,6 @@ import net.rptools.maptool.client.events.TokenHoverEnter; import net.rptools.maptool.client.ui.token.AbstractTokenOverlay; import net.rptools.maptool.client.ui.token.BarTokenOverlay; -import net.rptools.maptool.model.PermissionsScope; -import net.rptools.maptool.model.Token; -import net.rptools.maptool.model.TokenProperty; import net.rptools.maptool.model.player.Player; import net.rptools.maptool.util.HTMLUtil; import net.rptools.maptool.util.ImageManager; @@ -250,7 +247,7 @@ public StatSheetContext(TokenHoverEnter hoverEvent, Player player, StatSheetLoca .getTokenPropertyList(token.getPropertyType()) .forEach( tp -> { - if (isPermitted(token, player, tp)) { + if (tp.getStatSheetViewPermission().hasPermission(player, token)) { Object value = token.getEvaluatedProperty(resolver, tp.getName()); //noinspection ConstantValue if (value == null || value instanceof String sValue && sValue.isBlank()) { @@ -289,46 +286,6 @@ public StatSheetContext(TokenHoverEnter hoverEvent, Player player, StatSheetLoca }; } - /** - * Simplistic check for alliance based on GM vs. players. With all players are on the same side, - * all PCs and player-owned NPCs are on the same side. - * - * @param token to check is ally - * @param player whose side we are checking for - * @return if token belongs to the same side - */ - private boolean isAllied(Token token, Player player) { - if (!player.isGM() && token.getType().equals(Token.Type.PC)) { - return true; - } else { - List teamMates = MapTool.getPlayerList().stream() - .dropWhile(p -> p.isGM() != player.isGM()) - .toList(); - final Set owners = token.getOwners(); - return !teamMates.stream().filter(p -> owners.contains(p.getName())).toList().isEmpty(); - } - } - - private boolean isPermitted(Token token, Player player, Object checkThis) { - if (checkThis instanceof TokenProperty tp) { - PermissionsScope permission = tp.getVisibilityPermission(); - return switch (permission) { - case ALL -> true; - case NONE -> false; - case GM -> player.isGM(); - case OWNER -> player.isGM() || token.isOwner(player.getName()); - case ALLIED -> player.isGM() || token.isOwner(player.getName()) || isAllied(token, player); - case OWNER_ONLY -> !player.isGM() && token.isOwner(player.getName()); - case ALLIED_ONLY -> !player.isGM() && !token.isOwner(player.getName()) && isAllied(token, player); - case OPPONENT_ONLY -> !player.isGM() && !token.isOwner(player.getName()) && !isAllied(token, player); - case null -> false; - }; - } else { - // not yet implemented - return false; - } - } - private static final Function getImageDimensions = md5Key -> { BufferedImage image = ImageManager.getImage(md5Key); @@ -465,7 +422,8 @@ public String getImage() { public String getPortrait() { return portraitAsset != null ? "asset://" + portraitAsset : null; } -/** + + /** * Returns the handout asset of the token. * * @return The portrait asset of the token. diff --git a/src/main/java/net/rptools/maptool/model/sheet/stats/StatSheetManager.java b/src/main/java/net/rptools/maptool/model/sheet/stats/StatSheetManager.java index 1491216184..b430a27967 100644 --- a/src/main/java/net/rptools/maptool/model/sheet/stats/StatSheetManager.java +++ b/src/main/java/net/rptools/maptool/model/sheet/stats/StatSheetManager.java @@ -185,8 +185,7 @@ public Set getStatSheets(String propertyType) { * @return the id of the stat sheet. */ public SortedSet getOrderedStatSheets(String propertyType) { - TreeSet sheets = - new TreeSet((s1, s2) -> StatSheetManager.compareStatSheets(s1, s2)); + TreeSet sheets = new TreeSet<>(StatSheetManager::compareStatSheets); sheets.addAll(getStatSheets(propertyType)); return sheets; } diff --git a/src/main/resources/net/rptools/maptool/client/ui/themes/AahLAF.properties b/src/main/resources/net/rptools/maptool/client/ui/themes/AahLAF.properties index c01e9b5a37..cf0fb302bf 100644 --- a/src/main/resources/net/rptools/maptool/client/ui/themes/AahLAF.properties +++ b/src/main/resources/net/rptools/maptool/client/ui/themes/AahLAF.properties @@ -12,6 +12,7 @@ @col90 = changeLightness(@col50, 10%) @col100= shade(@baseColour,80%) @blue = #2675bf +@green = #26bf75 @red = #c42b1c @yellow= #fcf2d7 @gradientEnd = mix(@col100,@col0,25%) @@ -169,6 +170,7 @@ ScrollBar.buttonArrowColor = @col60 ScrollBar.buttonDisabledArrowColor = fadein(@col90,40%,derived) ScrollBar.hoverButtonBackground = @col30 ScrollBar.minimumButtonSize = 10, 10 +ScrollBar.thumbBorderColor = @col60 Slider.focusWidth = 1 Slider.focusedColor = @col40 @@ -199,20 +201,22 @@ TabbedPane.tabSeparatorsFullHeight= true TabbedPane.underlineColor = @col50 TabbedPane.hoverColor = @col0 +Table.font = +1 Table.cellMargins = 3,3,3,3 # Table.selectionInsets Table.background = @col5 Table.foreground = @col80 -Table.selectionBackground = @col10 +Table.selectionBackground = @col20 Table.selectionForeground= @col90 -# Table.selectionInactiveBackground -# Table.selectionInactiveForeground -# Table.alternateRowColor = @col5 +Table.disabledForeground = @col100 +Table.selectionInactiveBackground = @col30 +Table.selectionInactiveForeground = @col100 +Table.alternateRowColor = @col10 Table.gridColor = @col50 Table.cellFocusColor = @blue -# Table.focusCellForeground -# Table.focusCellBackground -# Table.dropLineColor +Table.focusCellForeground = @red +Table.focusCellBackground = @blue +Table.dropLineColor = @red # Table.dropLineShortColor # Table.dropCellBackground # Table.dropCellForeground @@ -220,28 +224,30 @@ Table.showHorizontalLines = true Table.showVerticalLines = true Table.showTrailingVerticalLine = false Table.showCellFocusIndicator = true +Table.intercellSpacing = 2,1 +Table.rowHeight = 18 # Table.cellBorder = 0,0,0,0, 1 -# Table.focusCellHighlightBorder = 1,1,1,1 @red 1 -Table.focusSelectedCellHighlightBorder = 3,3,3,3, @red, 2, 6 -Table.paintOutsideAlternateRows = true +# Table.focusCellHighlightBorder = 3,3,3,3, @blue, 2, 6 +Table.focusSelectedCellHighlightBorder = 3,3,3,3, @blue, 2, 6 +# Table.paintOutsideAlternateRows = true # Table.editorSelectAllOnStartEditing # Table.consistentHomeEndKeyBehavior - - +Table.sortIconColor = @blue TableHeader.background = @col20 -TableHeader.foreground = @col80 +TableHeader.foreground = @col90 # TableHeader.hoverBackground # TableHeader.hoverForeground # TableHeader.pressedBackground # TableHeader.pressedForeground -Table.sortIconColor = @blue TableHeader.separatorColor = @col40 TableHeader.bottomSeparatorColor = @col60 TextComponent.arc = 8 +TitlePane.menuBarEmbedded = true + ToolBar.background = @col5 ToolBar.foreground = @col90 ToolBar.floatable = true @@ -252,4 +258,34 @@ ToolBar.separatorColor = @col90 ToolTip.background = @yellow ToolTip.foreground = @col90 -ToolTip.border = 3,3,5,3, @gradientBorderEnd, 1, 6 \ No newline at end of file +ToolTip.border = 3,3,5,3, @gradientBorderEnd, 1, 6 + +#Tree.rowHeight = 24 +Tree.showsRootHandles = true +Tree.repaintWholeRow = true +# Tree.selectionBorderColor Color focus indicator border color +Tree.drawsFocusBorderAroundIcon = true +# Tree.drawDashedFocusIndicator boolean +Tree.rendererFillBackground = true +#Tree.rendererMargins Insets +#Tree.dropCellBackground Color +#Tree.dropCellForeground Color +#Tree.editorBorder Border +#Tree.editorBorderSelectionColor Color +#Tree.border Border +#Tree.selectionBackground Color +#Tree.selectionForeground Color +#Tree.selectionInactiveBackground Color +#Tree.selectionInactiveForeground Color +#Tree.alternateRowColor Color +#Tree.selectionInsets Insets +#Tree.selectionArc int +Tree.wideSelection = true +Tree.wideCellRenderer = true +Tree.showCellFocusIndicator = true +Tree.showDefaultIcons = true +Tree.icon.expandedColor = @red +Tree.icon.collapsedColor = @green +#Tree.icon.leafColor Color +#Tree.icon.closedColor Color +#Tree.icon.openColor = @green \ 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 2984b5fe06..2858739664 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -216,15 +216,6 @@ Color.none = None Color.custom = Custom Color.default = Default -variable.type.undefined = Undefined -variable.type.json = JSON -variable.type.jsonArray = JSON Array -variable.type.jsonObject = JSON Object -variable.type.number = Number -variable.type.string = String -variable.type.stringList = String List -variable.type.stringPropList = String Property List - Default.campaign.tokenPropertyType = Basic Default.campaign.tokenProperty.name.strength = Strength Default.campaign.tokenProperty.name.dexterity = Dexterity @@ -1069,23 +1060,23 @@ Button.networkingHelp = Networking Help Button.networkingHelp.mnemonic = {f1} ServerDialog.generatePassword = Generate Password -permissionsScope.displayName.none = None -permissionsScope.displayName.gm = GM -permissionsScope.displayName.owner = Owner -permissionsScope.displayName.allies = Allies -permissionsScope.displayName.all = All -permissionsScope.displayName.owner.discrete = Owner Only -permissionsScope.displayName.allies.discrete = Allies Only -permissionsScope.displayName.opponent = Opponents - -permissionsScope.description.none = Nobody has permission. -permissionsScope.description.gm = Only the GM has permission. -permissionsScope.description.owner = The GM and Owner have permission. -permissionsScope.description.allies = The GM, Owner and their Allies have permission. -permissionsScope.description.all = Everyone has permission. -permissionsScope.description.owner.discrete = Only the Owner has permission. -permissionsScope.description.allies.discrete = Only the Owner's Allies have permission. -permissionsScope.description.opponent = Only the Owner's Opponents have permission. +permission.displayName.none = None +permission.displayName.gm = GM +permission.displayName.owner = Owner +permission.displayName.allies = Allies +permission.displayName.all = All +permission.displayName.owner.discrete = Owner Only +permission.displayName.allies.discrete = Allies Only +permission.displayName.opponent = Opponents + +permission.description.none = Nobody has permission. +permission.description.gm = Only the GMs have permission. +permission.description.owner = The GMs and Owners have permission. +permission.description.allies = The GMs, Owners and the Owners' Allies have permission. +permission.description.all = Everyone has permission. +permission.description.owner.discrete = Only the Owner has permission. +permission.description.allies.discrete = Only the Owner's Allies have permission. +permission.description.opponent = Only the Owner's Opponents have permission. CampaignPropertiesDialog.tab.token = Token Properties CampaignPropertiesDialog.tab.repo = Repositories @@ -1114,18 +1105,18 @@ campaignProperties.macroEditDialog.default.title = Default value for Property campaignPropertiesTable.column.name = Name campaignPropertiesTable.column.shortName = Short Name campaignPropertiesTable.column.displayName = Display Name +campaignPropertiesTable.column.playerViewable = Player Viewable campaignPropertiesTable.column.playerEditable = Player Editable campaignPropertiesTable.column.statSheetVisibility = Stat-Sheet campaignPropertiesTable.column.defaultValue = Default -campaignPropertiesTable.column.valueType = Value Type campaignPropertiesTable.column.name.description = The actual name of the property, used by the application. campaignPropertiesTable.column.shortName.description = Short version of the property name. Used in stat-sheets and character sheets. campaignPropertiesTable.column.displayName.description = Display version of the property name. Used in stat-sheets and character sheets. -campaignPropertiesTable.column.statSheet.playerEditable = Show the property to players in the Edit Token dialog. +campaignPropertiesTable.column.playerViewable.description = Show the property to players in the Edit Token dialog. +campaignPropertiesTable.column.playerEditable.description = Allow players to edit in the Edit Token dialog. campaignPropertiesTable.column.statSheet.description = Define who has permission to see the property on the stat-sheet. campaignPropertiesTable.column.default.description = The default value assigned to the property. -campaignPropertiesTable.column.valueType.description = The type of value, e.g. number/JSON. # Bar propertyType CampaignPropertiesDialog.combo.bars.type.twoImages = Two Images diff --git a/src/test/java/net/rptools/maptool/model/TokenPropertiesTest.java b/src/test/java/net/rptools/maptool/model/TokenPropertiesTest.java index e66e3c1662..c82131ec3d 100644 --- a/src/test/java/net/rptools/maptool/model/TokenPropertiesTest.java +++ b/src/test/java/net/rptools/maptool/model/TokenPropertiesTest.java @@ -38,18 +38,15 @@ public class TokenPropertiesTest { public void setUp() { propsList = new ArrayList<>(); propsList.add(new TokenProperty("prop1", null, null, null, "10")); - propsList.add(new TokenProperty("prop2", null, null, PermissionsScope.ALL, "{prop2=prop1}")); + propsList.add(new TokenProperty("prop2", null, null, Permissions.ALL, "{prop2=prop1}")); propsList.add( new TokenProperty( - "jsonObj1", - null, - PermissionsScope.ALL, - "{\"sampleKey\": 5, \"otherKey\": \"theValue\"}")); - propsList.add(new TokenProperty("jsonObj2", null, PermissionsScope.ALL, "{\"prop3\"=other}")); - propsList.add(new TokenProperty("jsonObj3", null, PermissionsScope.ALL, "{prop3:other}")); - propsList.add(new TokenProperty("jsonArr1", null, PermissionsScope.ALL, "[4, 3]")); - propsList.add(new TokenProperty("plainStr1", null, PermissionsScope.ALL, "justAString")); - propsList.add(new TokenProperty("badJson", null, PermissionsScope.ALL, "{\"a\": 1}{\"b\": 2}")); + "jsonObj1", null, Permissions.ALL, "{\"sampleKey\": 5, \"otherKey\": \"theValue\"}")); + propsList.add(new TokenProperty("jsonObj2", null, Permissions.ALL, "{\"prop3\"=other}")); + propsList.add(new TokenProperty("jsonObj3", null, Permissions.ALL, "{prop3:other}")); + propsList.add(new TokenProperty("jsonArr1", null, Permissions.ALL, "[4, 3]")); + propsList.add(new TokenProperty("plainStr1", null, Permissions.ALL, "justAString")); + propsList.add(new TokenProperty("badJson", null, Permissions.ALL, "{\"a\": 1}{\"b\": 2}")); MapTool.getCampaign().putTokenType("testType", propsList); testToken = new Token(); From 7d450ca3ebeb274a9a8312ef8c1c25a42d16635b Mon Sep 17 00:00:00 2001 From: bubblobill Date: Fri, 17 Jul 2026 17:14:21 +0800 Subject: [PATCH 03/12] Missing classes. Passable tests. --- .../table/MultiLineTableHeaderRenderer.java | 13 ++- .../rptools/maptool/model/DisplayNames.java | 33 ++++++ .../rptools/maptool/model/Permissions.java | 107 ++++++++++++++++++ .../CampaignPropertiesDialogTest.java | 2 +- .../maptool/model/TokenPropertiesTest.java | 4 +- 5 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/model/DisplayNames.java create mode 100644 src/main/java/net/rptools/maptool/model/Permissions.java diff --git a/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java b/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java index 10dd1a0823..d5c6401700 100644 --- a/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java +++ b/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java @@ -30,8 +30,17 @@ public class MultiLineTableHeaderRenderer implements TableCellRenderer { static { Color mixWith = UIManager.getColor(FlatIconColors.OBJECTS_BLACK_TEXT.key); - HEADER_ALTERNATE_BACKGROUND = ColorFunctions.mix(HEADER_BACKGROUND, mixWith, 0.94f); - HEADER_ALTERNATE_FOREGROUND = ColorFunctions.mix(HEADER_FOREGROUND, mixWith, 0.31f); + // this is so tests can be passed as they do not install the LaF + Color c1, c2; + try { + c1 = ColorFunctions.mix(HEADER_BACKGROUND, mixWith, 0.94f); + c2 = ColorFunctions.mix(HEADER_FOREGROUND, mixWith, 0.31f); + } catch (Exception e) { + c1 = Color.decode("#dfddd2"); + c2 = Color.BLACK; + } + HEADER_ALTERNATE_BACKGROUND = c1; + HEADER_ALTERNATE_FOREGROUND = c2; } public MultiLineTableHeaderRenderer() {} diff --git a/src/main/java/net/rptools/maptool/model/DisplayNames.java b/src/main/java/net/rptools/maptool/model/DisplayNames.java new file mode 100644 index 0000000000..3fd4e3dc9e --- /dev/null +++ b/src/main/java/net/rptools/maptool/model/DisplayNames.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; + +public interface DisplayNames { + String getName(); + + String getDisplayName(); + + String getShortName(); + + void setName(String value); + + void setDisplayName(String value); + + void setShortName(String value); + + boolean hasShortName(); + + boolean hasDisplayName(); +} diff --git a/src/main/java/net/rptools/maptool/model/Permissions.java b/src/main/java/net/rptools/maptool/model/Permissions.java new file mode 100644 index 0000000000..7b0b1adcb9 --- /dev/null +++ b/src/main/java/net/rptools/maptool/model/Permissions.java @@ -0,0 +1,107 @@ +/* + * 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 java.util.List; +import java.util.Set; +import javax.annotation.Nullable; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.language.I18N; +import net.rptools.maptool.model.player.Player; + +/** + * + * + *

Defined Player permissions

+ * + *
  • Accommodates ordinary RPG use for GM vs Players + *
  • Adds a level {@link #ALLIED} where friendlies have permission as a step before {@link #ALL} + *
  • This opens the door for Teams of Players + *
  • Introduces exclusive permissions beyond GM Only + * + *

    Used in {@link TokenProperty} for determining who can see a property on a pop-up attribute + * sheet, and whether a property appears in the token property editor. + */ +public enum Permissions { + // Order follows increasing visibility for accumulated permissions + + /** Nobody has permission */ + NONE("permission.displayName.none"), + /** Only the GMs have permission */ + GM("permission.displayName.gm"), + /** The GMs and the Owners have permission */ + OWNER("permission.displayName.owner"), + /** The GMs, Owners, and the Owners' Allies have permission */ + ALLIED("permission.displayName.allies"), + /** Everyone has permission */ + ALL("permission.displayName.all"), + + // Exclusive permissions, i.e. Can exclude the GM, etc. + /** Only the Owners have permission */ + OWNER_ONLY("permission.displayName.owner.discrete"), + /** Only the Owners' Allies have permission */ + ALLIED_ONLY("permission.displayName.allies.discrete"), + /** Only the Owners and the Owners' Allies have permission */ + OWNER_ALLIED_ONLY("permission.displayName.allies.discrete"), + /** Only the Owners' Opponents have permission */ + OPPONENT_ONLY("permission.displayName.opponent"); + + final String displayName; + + Permissions(String i18nKey) { + this.displayName = I18N.getText(i18nKey); + } + + public boolean hasPermission(@Nullable Player player, @Nullable Token token) { + if (player == null || token == null) { + return this.equals(ALL); + } + return switch (this) { + case ALL -> true; + case NONE -> false; + case GM -> player.isGM(); + case OWNER -> player.isGM() || token.isOwner(player.getName()); + case ALLIED -> player.isGM() || token.isOwner(player.getName()) || isAllied(token, player); + case OWNER_ONLY -> token.isOwner(player.getName()); + case ALLIED_ONLY -> isAllied(token, player); + case OWNER_ALLIED_ONLY -> token.isOwner(player.getName()) || isAllied(token, player); + case OPPONENT_ONLY -> !token.isOwner(player.getName()) && !isAllied(token, player); + }; + } + + /** + * Simplistic check for alliance based on GM vs. players where all players on the same side. All + * PCs and player-owned NPCs are on the same side. + * + * @param token to check is ally + * @param player whose side we are checking for + * @return if token belongs to the same side + */ + private boolean isAllied(Token token, Player player) { + if (!player.isGM() && token.getType().equals(Token.Type.PC)) { + return true; + } else { + List teamMates = + MapTool.getPlayerList().stream().dropWhile(p -> p.isGM() != player.isGM()).toList(); + final Set owners = token.getOwners(); + return !teamMates.stream().filter(p -> owners.contains(p.getName())).toList().isEmpty(); + } + } + + @Override + public String toString() { + return displayName; + } +} diff --git a/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java b/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java index a5df6ea590..f1ceff00dd 100644 --- a/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java +++ b/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java @@ -15,7 +15,7 @@ package net.rptools.maptool.client.ui.campaignproperties; import static org.junit.jupiter.api.Assertions.assertEquals; - +import net.rptools.maptool.client.swing.table.*; import java.io.File; import java.lang.reflect.InvocationTargetException; import javax.swing.JButton; diff --git a/src/test/java/net/rptools/maptool/model/TokenPropertiesTest.java b/src/test/java/net/rptools/maptool/model/TokenPropertiesTest.java index c82131ec3d..8731165261 100644 --- a/src/test/java/net/rptools/maptool/model/TokenPropertiesTest.java +++ b/src/test/java/net/rptools/maptool/model/TokenPropertiesTest.java @@ -37,8 +37,8 @@ public class TokenPropertiesTest { @BeforeEach public void setUp() { propsList = new ArrayList<>(); - propsList.add(new TokenProperty("prop1", null, null, null, "10")); - propsList.add(new TokenProperty("prop2", null, null, Permissions.ALL, "{prop2=prop1}")); + propsList.add(new TokenProperty("prop1", null, null, true, false, Permissions.NONE, "10")); + propsList.add(new TokenProperty("prop2", null, null, false, Permissions.ALL, "{prop2=prop1}")); propsList.add( new TokenProperty( "jsonObj1", null, Permissions.ALL, "{\"sampleKey\": 5, \"otherKey\": \"theValue\"}")); From b3c7ab2d49e6d09745dc516782165adb19687675 Mon Sep 17 00:00:00 2001 From: bubblobill Date: Fri, 17 Jul 2026 17:17:07 +0800 Subject: [PATCH 04/12] formatting --- .../client/swing/table/MultiLineTableHeaderRenderer.java | 2 +- .../ui/campaignproperties/CampaignPropertiesDialogTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java b/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java index d5c6401700..dfda544165 100644 --- a/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java +++ b/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java @@ -37,7 +37,7 @@ public class MultiLineTableHeaderRenderer implements TableCellRenderer { c2 = ColorFunctions.mix(HEADER_FOREGROUND, mixWith, 0.31f); } catch (Exception e) { c1 = Color.decode("#dfddd2"); - c2 = Color.BLACK; + c2 = Color.BLACK; } HEADER_ALTERNATE_BACKGROUND = c1; HEADER_ALTERNATE_FOREGROUND = c2; diff --git a/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java b/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java index f1ceff00dd..4335aeb994 100644 --- a/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java +++ b/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java @@ -15,13 +15,14 @@ package net.rptools.maptool.client.ui.campaignproperties; import static org.junit.jupiter.api.Assertions.assertEquals; -import net.rptools.maptool.client.swing.table.*; + import java.io.File; import java.lang.reflect.InvocationTargetException; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.SwingUtilities; import net.rptools.maptool.client.AppConstants; +import net.rptools.maptool.client.swing.table.*; import net.rptools.maptool.language.I18N; import org.junit.jupiter.api.Test; From 1ad53f945214387b406b1e73f21d22ea557011a8 Mon Sep 17 00:00:00 2001 From: bubblobill Date: Fri, 17 Jul 2026 17:42:34 +0800 Subject: [PATCH 05/12] added license verification to stop test failure --- .../ui/campaignproperties/CampaignPropertiesDialogTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java b/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java index 4335aeb994..779edd07d3 100644 --- a/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java +++ b/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java @@ -27,6 +27,10 @@ import org.junit.jupiter.api.Test; public class CampaignPropertiesDialogTest { + { + com.jidesoft.utils.Lm.verifyLicense( + "Trevor Croft", "rptools", "5MfIVe:WXJBDrToeLWPhMv3kI2s3VFo"); + } @Test public void importPredefinedButton() throws InterruptedException, InvocationTargetException { From 56bd30f6c6da597d37b81dbf3de549cd2cfb4c3b Mon Sep 17 00:00:00 2001 From: bubblobill Date: Fri, 17 Jul 2026 17:49:15 +0800 Subject: [PATCH 06/12] maybe this --- .../CampaignPropertiesDialogTest.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java b/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java index 779edd07d3..fe9c1e8075 100644 --- a/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java +++ b/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java @@ -27,15 +27,12 @@ import org.junit.jupiter.api.Test; public class CampaignPropertiesDialogTest { - { - com.jidesoft.utils.Lm.verifyLicense( - "Trevor Croft", "rptools", "5MfIVe:WXJBDrToeLWPhMv3kI2s3VFo"); - } - @Test public void importPredefinedButton() throws InterruptedException, InvocationTargetException { SwingUtilities.invokeAndWait( () -> { + com.jidesoft.utils.Lm.verifyLicense( + "Trevor Croft", "rptools", "5MfIVe:WXJBDrToeLWPhMv3kI2s3VFo"); CampaignPropertiesDialog cpd = new CampaignPropertiesDialog(); JButton button = cpd.getImportPredefinedButton(); @@ -50,6 +47,8 @@ public void predefinedPropertiesComboBox_noFiles() throws InterruptedException, InvocationTargetException { SwingUtilities.invokeAndWait( () -> { + com.jidesoft.utils.Lm.verifyLicense( + "Trevor Croft", "rptools", "5MfIVe:WXJBDrToeLWPhMv3kI2s3VFo"); CampaignPropertiesDialog cpd = new CampaignPropertiesDialog() { @Override @@ -69,6 +68,8 @@ public void predefinedPropertiesComboBox_twoFiles() throws InterruptedException, InvocationTargetException { SwingUtilities.invokeAndWait( () -> { + com.jidesoft.utils.Lm.verifyLicense( + "Trevor Croft", "rptools", "5MfIVe:WXJBDrToeLWPhMv3kI2s3VFo"); String one = new String("a" + AppConstants.CAMPAIGN_PROPERTIES_FILE_EXTENSION); String two = new String("b" + AppConstants.CAMPAIGN_PROPERTIES_FILE_EXTENSION); From 5e7befdb80e8fa85e77816f3fb3715be15c6b896 Mon Sep 17 00:00:00 2001 From: bubblobill Date: Fri, 17 Jul 2026 18:39:09 +0800 Subject: [PATCH 07/12] or this --- .../CampaignPropertiesDialogTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java b/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java index fe9c1e8075..d850fe262b 100644 --- a/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java +++ b/src/test/java/net/rptools/maptool/client/ui/campaignproperties/CampaignPropertiesDialogTest.java @@ -24,15 +24,20 @@ import net.rptools.maptool.client.AppConstants; import net.rptools.maptool.client.swing.table.*; import net.rptools.maptool.language.I18N; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class CampaignPropertiesDialogTest { + @BeforeAll + public static void register() { + com.jidesoft.utils.Lm.verifyLicense( + "Trevor Croft", "rptools", "5MfIVe:WXJBDrToeLWPhMv3kI2s3VFo"); + } + @Test public void importPredefinedButton() throws InterruptedException, InvocationTargetException { SwingUtilities.invokeAndWait( () -> { - com.jidesoft.utils.Lm.verifyLicense( - "Trevor Croft", "rptools", "5MfIVe:WXJBDrToeLWPhMv3kI2s3VFo"); CampaignPropertiesDialog cpd = new CampaignPropertiesDialog(); JButton button = cpd.getImportPredefinedButton(); @@ -47,8 +52,6 @@ public void predefinedPropertiesComboBox_noFiles() throws InterruptedException, InvocationTargetException { SwingUtilities.invokeAndWait( () -> { - com.jidesoft.utils.Lm.verifyLicense( - "Trevor Croft", "rptools", "5MfIVe:WXJBDrToeLWPhMv3kI2s3VFo"); CampaignPropertiesDialog cpd = new CampaignPropertiesDialog() { @Override @@ -68,8 +71,6 @@ public void predefinedPropertiesComboBox_twoFiles() throws InterruptedException, InvocationTargetException { SwingUtilities.invokeAndWait( () -> { - com.jidesoft.utils.Lm.verifyLicense( - "Trevor Croft", "rptools", "5MfIVe:WXJBDrToeLWPhMv3kI2s3VFo"); String one = new String("a" + AppConstants.CAMPAIGN_PROPERTIES_FILE_EXTENSION); String two = new String("b" + AppConstants.CAMPAIGN_PROPERTIES_FILE_EXTENSION); @@ -77,7 +78,6 @@ public void predefinedPropertiesComboBox_twoFiles() new CampaignPropertiesDialog() { @Override protected File[] getPredefinedPropertyFiles(File propertyDir) { - return new File[] {new File(one), new File(two)}; } }; From fef631761fcd6bda923750150e68c6d7f1b667a7 Mon Sep 17 00:00:00 2001 From: bubblobill Date: Fri, 17 Jul 2026 19:25:15 +0800 Subject: [PATCH 08/12] or this --- .../maptool/language/i18n_en_AU.properties | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 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 4d7e8d31c1..243d981ea7 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 @@ -889,18 +889,6 @@ ServerDialog.option.rolls.tooltip = Tool Tips will be used for [ ] rolls wh ServerDialog.button.networkinghelp = Networking Help ServerDialog.generatePassword = Generate Password -permissionsScope.displayName.none=None -permissionsScope.displayName.gm=GM -permissionsScope.displayName.owner=Owner -permissionsScope.displayName.allies=Allies -permissionsScope.displayName.all=All - -permissionsScope.description.none=Nobody has permission. -permissionsScope.description.owner=The Owner also has permission. -permissionsScope.description.gm=Only the GM has permission. -permissionsScope.description.allies=The Owner's Allies also have permission. -permissionsScope.description.all=Everyone has permission. - CampaignPropertiesDialog.tab.token = Token Properties CampaignPropertiesDialog.tab.repo = Repositories @@ -2945,4 +2933,22 @@ advanced.roll.propertyNotNumber = Property {0} is not a number. advanced.roll.noTokenInContext = No token in context. advanced.roll.inputNotNumber = Input {0} is not a number. Preferences.label.tokens.stack.hide=Hide token stack indicator -Preferences.label.tokens.stack.hide.tooltip=Token Layer stack indicator will be hidden \ No newline at end of file +Preferences.label.tokens.stack.hide.tooltip=Token Layer stack indicator will be hidden + +permission.displayName.none = None +permission.displayName.gm = GM +permission.displayName.owner = Owner +permission.displayName.allies = Allies +permission.displayName.all = All +permission.displayName.owner.discrete = Owner Only +permission.displayName.allies.discrete = Allies Only +permission.displayName.opponent = Opponents + +permission.description.none = Nobody has permission. +permission.description.gm = Only the GMs have permission. +permission.description.owner = The GMs and Owners have permission. +permission.description.allies = The GMs, Owners and the Owners' Allies have permission. +permission.description.all = Everyone has permission. +permission.description.owner.discrete = Only the Owner has permission. +permission.description.allies.discrete = Only the Owner's Allies have permission. +permission.description.opponent = Only the Owner's Opponents have permission. From 0a2854e145c59c434b3a5490b21c7e1678be04eb Mon Sep 17 00:00:00 2001 From: bubblobill Date: Fri, 17 Jul 2026 19:42:31 +0800 Subject: [PATCH 09/12] or this --- .../maptool/client/swing/table/WordWrapCellRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java b/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java index 41c6b73c2e..9ccdea7f3f 100644 --- a/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java +++ b/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java @@ -42,7 +42,7 @@ public WordWrapCellRenderer() { new File(AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); Theme theme = Theme.load(new FileInputStream(themeFile)); theme.apply(this); - setFont(FlatUIUtils.nonUIResource(UIManager.getFont("monospaced.font"))); + revalidate(); } catch (IOException e) { log.error("Error while loading theme", e); From 7b02bcc0b692993712d4b08fb1d6caab8857eb99 Mon Sep 17 00:00:00 2001 From: bubblobill Date: Fri, 17 Jul 2026 19:44:36 +0800 Subject: [PATCH 10/12] or this --- .../rptools/maptool/client/swing/table/WordWrapCellRenderer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java b/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java index 9ccdea7f3f..04979aa02b 100644 --- a/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java +++ b/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java @@ -14,7 +14,6 @@ */ package net.rptools.maptool.client.swing.table; -import com.formdev.flatlaf.ui.FlatUIUtils; import java.awt.*; import java.io.File; import java.io.FileInputStream; From 27a4999e8b8f0798dc6cb29d6a6ee24ffff3037a Mon Sep 17 00:00:00 2001 From: bubblobill Date: Fri, 17 Jul 2026 19:49:46 +0800 Subject: [PATCH 11/12] or these --- .../client/swing/table/MultiLineTableHeaderRenderer.java | 4 ++-- .../maptool/client/swing/table/WordWrapCellRenderer.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java b/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java index dfda544165..826718e335 100644 --- a/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java +++ b/src/main/java/net/rptools/maptool/client/swing/table/MultiLineTableHeaderRenderer.java @@ -29,14 +29,14 @@ public class MultiLineTableHeaderRenderer implements TableCellRenderer { private static final Color HEADER_ALTERNATE_FOREGROUND; static { - Color mixWith = UIManager.getColor(FlatIconColors.OBJECTS_BLACK_TEXT.key); // this is so tests can be passed as they do not install the LaF Color c1, c2; try { + Color mixWith = UIManager.getColor(FlatIconColors.OBJECTS_BLACK_TEXT.key); c1 = ColorFunctions.mix(HEADER_BACKGROUND, mixWith, 0.94f); c2 = ColorFunctions.mix(HEADER_FOREGROUND, mixWith, 0.31f); } catch (Exception e) { - c1 = Color.decode("#dfddd2"); + c1 = Color.WHITE; c2 = Color.BLACK; } HEADER_ALTERNATE_BACKGROUND = c1; diff --git a/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java b/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java index 04979aa02b..41c6b73c2e 100644 --- a/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java +++ b/src/main/java/net/rptools/maptool/client/swing/table/WordWrapCellRenderer.java @@ -14,6 +14,7 @@ */ package net.rptools.maptool.client.swing.table; +import com.formdev.flatlaf.ui.FlatUIUtils; import java.awt.*; import java.io.File; import java.io.FileInputStream; @@ -41,7 +42,7 @@ public WordWrapCellRenderer() { new File(AppConstants.THEMES_DIR, AppPreferences.defaultMacroEditorTheme.get() + ".xml"); Theme theme = Theme.load(new FileInputStream(themeFile)); theme.apply(this); - + setFont(FlatUIUtils.nonUIResource(UIManager.getFont("monospaced.font"))); revalidate(); } catch (IOException e) { log.error("Error while loading theme", e); From e344f97cee3c79d4bcdbcfb0055245b597bbb8d3 Mon Sep 17 00:00:00 2001 From: bubblobill Date: Fri, 17 Jul 2026 22:36:26 +0800 Subject: [PATCH 12/12] or these --- .../TokenPropertiesManagementPanelView.form | 38 +++++++++++++++---- .../maptool/language/i18n_en_AU.properties | 4 +- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanelView.form b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanelView.form index e99355fe45..fb316077ca 100644 --- a/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanelView.form +++ b/src/main/java/net/rptools/maptool/client/ui/campaignproperties/TokenPropertiesManagementPanelView.form @@ -23,7 +23,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -115,6 +115,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -123,22 +147,22 @@ - + - + - + - + @@ -147,7 +171,7 @@ - + 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 243d981ea7..cafa103a9c 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 @@ -919,13 +919,15 @@ campaignPropertiesTable.column.displayName = Display Name campaignPropertiesTable.column.statSheetVisibility = Stat-Sheet View campaignPropertiesTable.column.defaultValue = Default campaignPropertiesTable.column.playerEditable = Player Editable +campaignPropertiesTable.column.playerViewable = Player Viewable campaignPropertiesTable.column.name.description = The actual name of the property, used by the application. campaignPropertiesTable.column.shortName.description = Short version of the property name. Used in stat-sheets and character sheets. campaignPropertiesTable.column.displayName.description = Display version of the property name. Used in stat-sheets and character sheets. campaignPropertiesTable.column.statSheet.description = Define who has permission to see the property on the stat-sheet. campaignPropertiesTable.column.default.description = The default value assigned to the property. -campaignPropertiesTable.column.statSheet.playerEditable = Show the property to players in the Edit Token dialogue. +campaignPropertiesTable.column.playerViewable.description = Show the property to players in the Edit Token dialogue. +campaignPropertiesTable.column.playerEditable.description = Allow players to edit in the Edit Token dialogue.