diff --git a/src/main/java/net/rptools/maptool/client/AppPreferences.java b/src/main/java/net/rptools/maptool/client/AppPreferences.java index 1dbfece17d..21624d84fa 100644 --- a/src/main/java/net/rptools/maptool/client/AppPreferences.java +++ b/src/main/java/net/rptools/maptool/client/AppPreferences.java @@ -745,6 +745,9 @@ public static PreferenceStore getAppPreferenceStore() { "Preferences.label.loadMRU.tooltip", false); + public static final Preference initiativePanelShowsInfoInTitle = + store.defineBoolean("initShowTitleInfo", true); + public static final Preference initiativePanelShowsTokenImage = store.defineBoolean("initShowTokens", true); diff --git a/src/main/java/net/rptools/maptool/client/ui/tokenpanel/InitiativePanel.java b/src/main/java/net/rptools/maptool/client/ui/tokenpanel/InitiativePanel.java index 5e5032b912..9a5f53e6e1 100644 --- a/src/main/java/net/rptools/maptool/client/ui/tokenpanel/InitiativePanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/tokenpanel/InitiativePanel.java @@ -14,17 +14,18 @@ */ package net.rptools.maptool.client.ui.tokenpanel; +import static net.rptools.maptool.client.ui.MapToolFrame.MTFrame.INITIATIVE; + import com.google.common.eventbus.Subscribe; +import com.jidesoft.docking.DockableFrame; +import com.jidesoft.docking.event.DockableFrameAdapter; +import com.jidesoft.docking.event.DockableFrameEvent; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; +import java.awt.event.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.ArrayList; -import java.util.Collections; +import java.util.*; import java.util.List; -import java.util.Set; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; @@ -32,6 +33,7 @@ import net.rptools.maptool.client.AppUtil; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.swing.SwingUtil; +import net.rptools.maptool.client.ui.MapToolFrame; import net.rptools.maptool.client.ui.theme.Icons; import net.rptools.maptool.client.ui.theme.RessourceManager; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; @@ -62,7 +64,7 @@ public class InitiativePanel extends JPanel * Instance Variables *-------------------------------------------------------------------------------------------*/ - /** Model containing all of the tokens in this initiative. */ + /** Model containing all the tokens in this initiative. */ private InitiativeList list; /** The model used to display a list in the panel; */ @@ -98,7 +100,7 @@ public class InitiativePanel extends JPanel /** The component that contains the initiative menu. */ private final JPopupMenu popupMenu; - /** The menu item that tells the GM if NPC's are visible. */ + /** The menu item that tells the GM if NPCs are visible. */ private JCheckBoxMenuItem hideNPCMenuItem; /** @@ -135,10 +137,29 @@ public class InitiativePanel extends JPanel */ private boolean initPanelButtonsDisabled; + /** Flag indicating that additional information should be shown in the title bar. */ + private boolean showTitleInfo = AppPreferences.initiativePanelShowsInfoInTitle.get(); + + /** Flag indicating that title bar change needs action. */ + private boolean showTitleInfoPending = true; + + /** Frame used in docking manager */ + private DockableFrame dockableFrame = null; + + /** Custom title component for {@link #dockableFrame} */ + private final JLabel dockableFrameTitleComponent = new JLabel(); + + /** Display text used in {@link #dockableFrameTitleComponent} & {@link #dockableFrame} titles */ + private static final String TITLE_LABEL_TEXT = "%s (%d, %d)"; + + /** Tooltip text used in {@link #dockableFrameTitleComponent} */ + private static final String TITLE_LABEL_TOOLTIP_TEXT = + "%s: %d
%s: %d"; + /*--------------------------------------------------------------------------------------------- * Constructor *-------------------------------------------------------------------------------------------*/ - /** Setup the menu */ + /** Set up the menu */ public InitiativePanel() { // Build the form and add it's component @@ -178,7 +199,7 @@ public InitiativePanel() { // Set up the list with an empty model displayList = - new JList() { + new JList<>() { @Override public boolean getScrollableTracksViewportWidth() { // Make sure the width of the list always tracks with the width of the panel. Without @@ -243,9 +264,13 @@ public boolean getScrollableTracksViewportWidth() { "initPanel.warnWhenResettingRoundCounter", TOGGLE_WARN_WHEN_RESETTING_COUNTER_ACTION); I18N.setAction("initPanel.next", NEXT_ACTION); I18N.setAction("initPanel.prev", PREV_ACTION); + I18N.setAction("initPanel.showTokenCountInTitle", SHOW_TOKEN_COUNT_IN_TITLE); + updateView(); new MapToolEventBus().getMainEventBus().register(this); + + SwingUtilities.invokeLater(() -> dockableFrame = getDockableFrame()); } private static class TextlessButton extends JButton { @@ -266,6 +291,7 @@ private static class TextlessButton extends JButton { * GM's and Player's properly */ public void updateView() { + updateFrameTitle(); displayList.setDragEnabled(hasGMPermission()); // Set up the buttons @@ -329,10 +355,16 @@ public void updateView() { popupMenu.addSeparator(); popupMenu.add(new JMenuItem(REMOVE_TOKEN_ACTION)); } // endif + if (MapTool.getPlayer().isGM()) { + popupMenu.addSeparator(); + JCheckBoxMenuItem showTitleInfoCB = new JCheckBoxMenuItem(SHOW_TOKEN_COUNT_IN_TITLE); + showTitleInfoCB.setSelected(AppPreferences.initiativePanelShowsInfoInTitle.get()); + popupMenu.add(showTitleInfoCB); + } valueChanged(null); } - /** Remove all of the tokens from the model and clear round and current */ + /** Remove all the tokens from the model and clear round and current */ public void clearTokens() { list.clearModel(); } @@ -402,6 +434,13 @@ public boolean isShowInitState() { return showInitState; } + /** + * @return Getter for showTitleInfo + */ + public boolean isShowTitleInfo() { + return showTitleInfo; + } + /** * @return Getter for model */ @@ -410,7 +449,7 @@ public InitiativeListModel getModel() { } /** - * Set the zone that we are currently working on. + * Set the zone we are working on. * * @param aZone The new zone */ @@ -442,15 +481,14 @@ public void setZone(Zone aZone) { public boolean hasOwnerPermission(Token token) { if (token == null) return false; if (hasGMPermission()) return true; - if (ownerPermissions + return ownerPermissions && (!MapTool.getServerPolicy().useStrictTokenManagement() - || token.isOwner(MapTool.getPlayer().getName()))) return true; - return false; + || token.isOwner(MapTool.getPlayer().getName())); } /** * See if the current player has permission to execute GM restricted actions. This is not - * related to so-called trusted macros in MTscript. + * related to so-called trusted macros in MT Script. * * @return The value true if this player has permission for all actions. */ @@ -503,7 +541,7 @@ public boolean isInitPanelButtonsDisabled() { * Updates the "Disable Panel Buttons" setting, and tweaks the Next/Previous button tooltips * appropriately. Updates the view. * - * @param initPanelButtonsDisabled + * @param initPanelButtonsDisabled disable panel buttons */ public void setInitPanelButtonsDisabled(boolean initPanelButtonsDisabled) { this.initPanelButtonsDisabled = initPanelButtonsDisabled; @@ -526,8 +564,7 @@ public void setInitPanelButtonsDisabled(boolean initPanelButtonsDisabled) { public boolean isMovementLocked(Token token) { if (!movementLock || list == null || list.getSize() == 0) return false; if (model.getCurrentTokenInitiative() == null) return true; - if (model.getCurrentTokenInitiative().getToken() == token) return false; - return true; + return model.getCurrentTokenInitiative().getToken() != token; } /** @@ -553,7 +590,87 @@ private void onInitiativeListChanged(InitiativeListChanged event) { int oldSize = model.getSize(); setList(list); - if (oldSize != model.getSize()) displayList.getSelectionModel().clearSelection(); + if (oldSize != model.getSize()) { + displayList.getSelectionModel().clearSelection(); + } + } + + public DockableFrame getDockableFrame() { + if (dockableFrame == null) { + MapToolFrame mapToolFrame = MapTool.getFrame(); + if (mapToolFrame == null) { // this happens on startup before everything gets loaded + return null; + } + dockableFrame = mapToolFrame.getFrame(INITIATIVE); + if (dockableFrame != null) { + dockableFrame.setTitleLabelComponent(dockableFrameTitleComponent); + + dockableFrame.addDockableFrameListener( + new DockableFrameAdapter() { + @Override + public void dockableFrameAdded(DockableFrameEvent dockableFrameEvent) { + super.dockableFrameAdded(dockableFrameEvent); + updateFrameTitle(); + } + + @Override + public void dockableFrameShown(DockableFrameEvent dockableFrameEvent) { + super.dockableFrameShown(dockableFrameEvent); + updateFrameTitle(); + } + + @Override + public void dockableFrameTabShown(DockableFrameEvent dockableFrameEvent) { + super.dockableFrameTabShown(dockableFrameEvent); + updateFrameTitle(); + } + }); + } + } + return dockableFrame; + } + + private void updateFrameTitle() { + if (!MapTool.getPlayer().isGM() || getDockableFrame() == null && !showTitleInfoPending) { + return; + } + SwingUtilities.invokeLater( + () -> { + String tooltip, text; + if (isShowTitleInfo()) { + int pc = 0, npc = 0; + if (list != null && list.getSize() > 0 && MapTool.getPlayer().isGM()) { + for (TokenInitiative ti : list.getTokens()) { + if (ti.getToken().getType().equals(Type.PC)) { + pc++; + } else { + npc++; + } + } + } + + text = String.format(TITLE_LABEL_TEXT, I18N.getText("panel.Initiative"), pc, npc); + tooltip = + String.format( + TITLE_LABEL_TOOLTIP_TEXT, + I18N.getText("Token.Type.PC"), + pc, + I18N.getText("Token.Type.NPC"), + npc); + dockableFrameTitleComponent.setText(text); + dockableFrameTitleComponent.setToolTipText(tooltip); + dockableFrame.setSideTitle(text); + dockableFrame.setTabTitle(text); + } else { + text = I18N.getText("panel.Initiative"); + tooltip = I18N.getText("panel.Initiative.description"); + dockableFrameTitleComponent.setText(text); + dockableFrameTitleComponent.setToolTipText(tooltip); + dockableFrame.setSideTitle(text); + dockableFrame.setTabTitle(text); + showTitleInfoPending = false; + } + }); } @Subscribe @@ -612,28 +729,29 @@ public void valueChanged(ListSelectionEvent e) { */ @Override public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(InitiativeList.ROUND_PROP)) { - updateRound(); - } else if (evt.getPropertyName().equals(InitiativeList.CURRENT_PROP)) { - Token t = list.getCurrentToken(); - if (t == null) return; - String s = I18N.getText("initPanel.displayMessage", t.getName()); - if (InitiativeListModel.isTokenVisible(t, list.isHideNPC()) - && t.getType() != Type.NPC - && AppPreferences.showInitiativeGainedMessage.get()) - MapTool.addMessage(TextMessage.say(null, s)); - displayList.ensureIndexIsVisible(model.getDisplayIndex(list.getCurrent())); - NEXT_ACTION.setEnabled( - !isInitPanelButtonsDisabled() && hasOwnerPermission(list.getCurrentToken())); - } else if (evt.getPropertyName().equals(InitiativeList.TOKENS_PROP)) { - if ((evt.getOldValue() == null && evt.getNewValue() instanceof TokenInitiative) - || (evt.getNewValue() == null & evt.getOldValue() instanceof TokenInitiative)) - displayList.getSelectionModel().clearSelection(); - } else if (evt.getPropertyName().equals(InitiativeList.HIDE_NPCS_PROP)) { - displayList.getSelectionModel().clearSelection(); - } else if (evt.getPropertyName().equals(InitiativeList.OWNER_PERMISSIONS_PROP)) { - updateView(); - } // endif + updateFrameTitle(); + switch (evt.getPropertyName()) { + case InitiativeList.ROUND_PROP -> updateRound(); + case InitiativeList.CURRENT_PROP -> { + Token t = list.getCurrentToken(); + if (t == null) return; + String s = I18N.getText("initPanel.displayMessage", t.getName()); + if (InitiativeListModel.isTokenVisible(t, list.isHideNPC()) + && t.getType() != Type.NPC + && AppPreferences.showInitiativeGainedMessage.get()) + MapTool.addMessage(TextMessage.say(null, s)); + displayList.ensureIndexIsVisible(model.getDisplayIndex(list.getCurrent())); + NEXT_ACTION.setEnabled( + !isInitPanelButtonsDisabled() && hasOwnerPermission(list.getCurrentToken())); + } + case InitiativeList.TOKENS_PROP -> { + if ((evt.getOldValue() == null && evt.getNewValue() instanceof TokenInitiative) + || (evt.getNewValue() == null & evt.getOldValue() instanceof TokenInitiative)) + displayList.getSelectionModel().clearSelection(); + } + case InitiativeList.HIDE_NPCS_PROP -> displayList.getSelectionModel().clearSelection(); + case InitiativeList.OWNER_PERMISSIONS_PROP -> updateView(); + } } /*--------------------------------------------------------------------------------------------- @@ -705,6 +823,18 @@ public void actionPerformed(ActionEvent e) { } }; + /** This action toggles the display of token counts in the title bar. */ + public final Action SHOW_TOKEN_COUNT_IN_TITLE = + new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + showTitleInfo = ((JCheckBoxMenuItem) e.getSource()).isSelected(); + showTitleInfoPending = true; + AppPreferences.initiativePanelShowsInfoInTitle.set(showTitleInfo); + updateFrameTitle(); + } + }; + /** This action toggles the display of token images. */ public final Action SHOW_TOKEN_STATES_ACTION = new AbstractAction() { @@ -776,7 +906,7 @@ public void actionPerformed(ActionEvent e) { } }; - /** This action will set the initiative state of the currently selected token. */ + /** This action will set the initiative state of the current selected token. */ public final Action SET_INIT_STATE_VALUE = new AbstractAction() { @Override @@ -788,7 +918,7 @@ public void actionPerformed(ActionEvent e) { if (hasGMPermission() && token != null && token.getGMName() != null - && token.getGMName().trim().length() != 0) + && !token.getGMName().trim().isEmpty()) sName += " (" + token.getGMName().trim() + ")"; String s = I18N.getText("initPanel.enterState", sName); String input = JOptionPane.showInputDialog(s, ti.getState()); @@ -797,7 +927,7 @@ public void actionPerformed(ActionEvent e) { } }; - /** This action will clear the initiative state of the currently selected token. */ + /** This action will clear the initiative state of the current selected token. */ public final Action CLEAR_INIT_STATE_VALUE = new AbstractAction() { @Override @@ -831,7 +961,7 @@ public void actionPerformed(ActionEvent e) { new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { - List tokens = new ArrayList(); + List tokens = new ArrayList<>(); for (Token token : list.getZone().getTokensForLayers(Zone.Layer::isTokenLayer)) { if (token.getType() == Type.PC) tokens.add(token); } // endfor diff --git a/src/main/java/net/rptools/maptool/model/InitiativeList.java b/src/main/java/net/rptools/maptool/model/InitiativeList.java index 2d4a79e966..047c006ef6 100644 --- a/src/main/java/net/rptools/maptool/model/InitiativeList.java +++ b/src/main/java/net/rptools/maptool/model/InitiativeList.java @@ -1023,4 +1023,8 @@ public int compare(TokenInitiative o1, TokenInitiative o2) { return comparison; } } + + public TokenInitiativeDescComparator getComparator() { + return new TokenInitiativeDescComparator(); + } } diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 7ca803a223..ad5d469661 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -1927,6 +1927,7 @@ initPanel.setInitState = Set Initiative... initPanel.showInitStates = Show Initiative initPanel.showTokenStates = Show Token States initPanel.showTokens = Show Tokens +initPanel.showTokenCountInTitle = Show Token Count in Title Bar initPanel.sort = Sort initPanel.sort.description = Sort the list by initiative values. Uses Descending order by default, with any non-numeric values coming after any numbers, and any blank values coming last. initPanel.toggleHideNPCs = Hide NPCs @@ -1957,7 +1958,6 @@ initiative.menu.remove = Remove initiative.menu.resume = Resume initiative.menu.setState = Set Initiative... - layer.token = Token layer.gm = Hidden layer.object = Object