/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.client.ui; import java.awt.Dimension; import java.awt.Image; import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.text.JTextComponent; import com.t3.client.AppActions; import com.t3.client.AppUtil; import com.t3.client.TabletopTool; import com.t3.client.tool.FacingTool; import com.t3.client.tool.PointerTool; import com.t3.client.tool.StampTool; import com.t3.client.ui.token.EditTokenDialog; import com.t3.client.ui.zone.ZoneRenderer; import com.t3.guid.GUID; import com.t3.language.I18N; import com.t3.model.Direction; import com.t3.model.Light; import com.t3.model.LightSource; import com.t3.model.Token; import com.t3.model.TokenFootprint; import com.t3.model.Zone; import com.t3.model.ZonePoint; import com.t3.model.grid.Grid; import com.t3.persistence.PersistenceUtil; import com.t3.util.ImageManager; import com.t3.util.TokenUtil; public abstract class AbstractTokenPopupMenu extends JPopupMenu { private static final long serialVersionUID = -3741870412603226747L; private final ZoneRenderer renderer; private final Token tokenUnderMouse; private boolean areTokensOwned; int x, y; Set<GUID> selectedTokenSet; public AbstractTokenPopupMenu(Set<GUID> selectedTokenSet, int x, int y, ZoneRenderer renderer, Token tokenUnderMouse) { super(); this.renderer = renderer; this.x = x; this.y = y; this.selectedTokenSet = selectedTokenSet; this.tokenUnderMouse = tokenUnderMouse; setOwnership(); } protected boolean tokensAreOwned() { return areTokensOwned; } private void setOwnership() { areTokensOwned = true; if (!TabletopTool.getPlayer().isGM() && TabletopTool.getServerPolicy().useStrictTokenManagement()) { for (GUID tokenGUID : selectedTokenSet) { Token token = getRenderer().getZone().getToken(tokenGUID); if (!token.isOwner(TabletopTool.getPlayer().getName())) { areTokensOwned = false; break; } } } } protected class ShowHandoutAction extends AbstractAction { public ShowHandoutAction() { putValue(Action.NAME, "Show Handout"); setEnabled(getTokenUnderMouse().getCharsheetImage() != null); } @Override public void actionPerformed(ActionEvent e) { AssetViewerDialog dialog = new AssetViewerDialog(getTokenUnderMouse().getName() + "'s Character Sheet", getTokenUnderMouse().getCharsheetImage()); dialog.pack(); dialog.setVisible(true); } } protected JMenu createLightSourceMenu() { JMenu menu = new JMenu("Light Source"); if (tokenUnderMouse.hasLightSources()) { menu.add(new ClearLightAction()); ZoneRenderer renderer = TabletopTool.getFrame().getCurrentZoneRenderer(); for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); if (token.hasLightSourceType(LightSource.Type.NORMAL)) { menu.add(new ClearLightsOnlyAction()); } if (token.hasLightSourceType(LightSource.Type.AURA)) { menu.add(new ClearAurasOnlyAction()); } if (token.hasGMAuras()) { menu.add(new ClearGMAurasOnlyAction()); } if (token.hasOwnerOnlyAuras()) { menu.add(new ClearOwnerAurasOnlyAction()); } } menu.addSeparator(); } for (Entry<String, Map<GUID, LightSource>> entry : TabletopTool.getCampaign().getLightSourcesMap().entrySet()) { JMenu subMenu = new JMenu(entry.getKey()); List<LightSource> lightSources = new ArrayList<LightSource>(entry.getValue().values()); LightSource[] lightSourceList = new LightSource[entry.getValue().size()]; lightSources.toArray(lightSourceList); Arrays.sort(lightSourceList); LIGHTSOURCES: for (LightSource lightSource : lightSourceList) { for (Light light : lightSource.getLightList()) { if (light.isGM() && !TabletopTool.getPlayer().isGM()) { continue LIGHTSOURCES; } } JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(new ToggleLightSourceAction(lightSource)); menuItem.setSelected(tokenUnderMouse.hasLightSource(lightSource)); subMenu.add(menuItem); } if (subMenu.getItemCount() != 0) { menu.add(subMenu); } } return menu; } protected Token getTokenUnderMouse() { return tokenUnderMouse; } protected JMenu createFlipMenu() { JMenu flipMenu = new JMenu("Flip"); flipMenu.add(new AbstractAction() { { putValue(NAME, "Horizontal"); } @Override public void actionPerformed(ActionEvent e) { for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); if (token == null) { continue; } token.setFlippedX(!token.isFlippedX()); renderer.flush(token); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); } TabletopTool.getFrame().refresh(); } }); flipMenu.add(new AbstractAction() { { putValue(NAME, "Vertical"); } @Override public void actionPerformed(ActionEvent e) { for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); if (token == null) { continue; } token.setFlippedY(!token.isFlippedY()); renderer.flush(token); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); } TabletopTool.getFrame().refresh(); } }); return flipMenu; } protected JMenu createChangeToMenu(Zone.Layer... types) { JMenu changeTypeMenu = new JMenu("Change to"); for (Zone.Layer layer : types) { changeTypeMenu.add(new JMenuItem(new ChangeTypeAction(layer))); } return changeTypeMenu; } protected JMenu createArrangeMenu() { JMenu arrangeMenu = new JMenu("Arrange"); JMenuItem bringToFrontMenuItem = new JMenuItem("Bring to Front"); bringToFrontMenuItem.addActionListener(new BringToFrontAction()); JMenuItem sendToBackMenuItem = new JMenuItem("Send to Back"); sendToBackMenuItem.addActionListener(new SendToBackAction()); arrangeMenu.add(bringToFrontMenuItem); arrangeMenu.add(sendToBackMenuItem); return arrangeMenu; } protected JMenu createSizeMenu() { JMenu sizeMenu = new JMenu("Size"); JCheckBoxMenuItem freeSize = new JCheckBoxMenuItem(new FreeSizeAction()); freeSize.setSelected(!tokenUnderMouse.isSnapToScale()); sizeMenu.add(freeSize); sizeMenu.addSeparator(); Grid grid = renderer.getZone().getGrid(); for (TokenFootprint footprint : grid.getFootprints()) { JMenuItem menuItem = new JCheckBoxMenuItem(new ChangeSizeAction(footprint)); if (tokenUnderMouse.isSnapToScale() && tokenUnderMouse.getFootprint(grid) == footprint) { menuItem.setSelected(true); } sizeMenu.add(menuItem); } return sizeMenu; } protected void addGMItem(Action action) { if (action == null) { return; } if (TabletopTool.getPlayer().isGM()) { add(new JMenuItem(action)); } } protected void addGMItem(JMenu menu) { if (menu == null) { return; } if (TabletopTool.getPlayer().isGM()) { add(menu); } } protected void addToggledGMItem(Action action, boolean checked) { if (action == null) { return; } if (TabletopTool.getPlayer().isGM()) { JCheckBoxMenuItem item = new JCheckBoxMenuItem(action); item.setSelected(checked); add(item); } } protected void addToggledOwnedItem(Action action, boolean checked) { if (action == null) { return; } JCheckBoxMenuItem item = new JCheckBoxMenuItem(action); item.setSelected(checked); item.setEnabled(areTokensOwned); add(item); } protected void addToggledItem(Action action, boolean checked) { if (action == null) { return; } JCheckBoxMenuItem item = new JCheckBoxMenuItem(action); item.setSelected(checked); add(item); } protected void addOwnedItem(Action action) { if (action == null) { return; } JMenuItem item = new JMenuItem(action); item.setEnabled(areTokensOwned); add(new JMenuItem(action)); } protected void addOwnedItem(JMenu menu) { if (menu == null) { return; } menu.setEnabled(areTokensOwned); add(menu); } protected ZoneRenderer getRenderer() { return renderer; } public void showPopup(JComponent component) { show(component, x, y); } public class ChangeTypeAction extends AbstractAction { private final Zone.Layer layer; public ChangeTypeAction(Zone.Layer layer) { putValue(Action.NAME, layer.toString()); this.layer = layer; } @Override public void actionPerformed(ActionEvent e) { for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); if (token == null) { continue; } token.setLayer(layer); switch (layer) { case BACKGROUND: case OBJECT: token.setShape(Token.TokenShape.TOP_DOWN); break; case TOKEN: Image image = ImageManager.getImage(token.getImageAssetId()); if (image == null || image == ImageManager.TRANSFERING_IMAGE) { token.setShape(Token.TokenShape.TOP_DOWN); } else { token.setShape(TokenUtil.guessTokenType(image)); } break; } renderer.flush(token); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); } renderer.repaint(); TabletopTool.getFrame().updateTokenTree(); } } public class FreeSizeAction extends AbstractAction { public FreeSizeAction() { putValue(Action.NAME, tokenUnderMouse.isStamp() ? "Free Size" : "Native Size"); } @Override public void actionPerformed(ActionEvent e) { for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); if (token == null) { continue; } token.setSnapToScale(false); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); } renderer.repaint(); } } public class CopyAction extends AbstractAction { public CopyAction() { putValue(Action.NAME, I18N.getText("action.copyTokens")); } @Override public void actionPerformed(ActionEvent e) { AppActions.copyTokens(selectedTokenSet); } } public class CutAction extends AbstractAction { public CutAction() { putValue(Action.NAME, I18N.getText("action.cutTokens")); } @Override public void actionPerformed(ActionEvent e) { AppActions.cutTokens(renderer.getZone(), selectedTokenSet); } } public class ToggleLightSourceAction extends AbstractAction { private final LightSource lightSource; public ToggleLightSourceAction(LightSource lightSource) { super(lightSource.getName()); this.lightSource = lightSource; } @Override public void actionPerformed(ActionEvent e) { ZoneRenderer renderer = TabletopTool.getFrame().getCurrentZoneRenderer(); for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); if (token == null) { continue; } if (token.hasLightSource(lightSource)) { token.removeLightSource(lightSource); } else { token.addLightSource(lightSource, Direction.CENTER); } // Cache clearing renderer.flush(token); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); renderer.getZone().putToken(token); renderer.repaint(); } } } public class SaveAction extends AbstractAction { public SaveAction() { super("Save ..."); if (selectedTokenSet.size() > 1) { setEnabled(false); } } @Override public void actionPerformed(ActionEvent e) { File defaultFile = new File(tokenUnderMouse.getName()); JFileChooser chooser = TabletopTool.getFrame().getSaveFileChooser(); chooser.setSelectedFile(defaultFile); if (chooser.showSaveDialog(TabletopTool.getFrame()) != JFileChooser.APPROVE_OPTION) { return; } File file = chooser.getSelectedFile(); // Auto-extension if (!file.getName().endsWith(Token.FILE_EXTENSION)) { file = new File(file.getAbsolutePath() + "." + Token.FILE_EXTENSION); } // Confirm if (file.exists()) { if (!TabletopTool.confirm("File exists, would you like to overwrite?")) { return; } } Token token = new Token(tokenUnderMouse); if (!TabletopTool.getPlayer().isGM()) { token.setGMNotes(""); } try { PersistenceUtil.saveToken(token, file); } catch (IOException ioe) { ioe.printStackTrace(); TabletopTool.showError("Could not save token: " + ioe); } } } public class SetFacingAction extends AbstractAction { public SetFacingAction() { super("Set Facing"); } @Override public void actionPerformed(ActionEvent e) { Toolbox toolbox = TabletopTool.getFrame().getToolbox(); FacingTool tool = (FacingTool) toolbox.getTool(FacingTool.class); tool.init(tokenUnderMouse, renderer.getOwnedTokens(selectedTokenSet)); toolbox.setSelectedTool(FacingTool.class); } } public class ClearFacingAction extends AbstractAction { public ClearFacingAction() { super("Clear Facing"); } @Override public void actionPerformed(ActionEvent e) { ZoneRenderer renderer = TabletopTool.getFrame().getCurrentZoneRenderer(); for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); token.setFacing(null); renderer.flush(token); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); } renderer.repaint(); } } public class ClearLightsOnlyAction extends AbstractAction { public ClearLightsOnlyAction() { super("Clear Lights Only"); } @Override public void actionPerformed(ActionEvent e) { ZoneRenderer renderer = TabletopTool.getFrame().getCurrentZoneRenderer(); for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); if (token.hasLightSourceType(LightSource.Type.NORMAL)) { token.removeLightSourceType(LightSource.Type.NORMAL); } renderer.flush(token); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); renderer.getZone().putToken(token); } renderer.repaint(); } } public class ClearAurasOnlyAction extends AbstractAction { public ClearAurasOnlyAction() { super("Clear Auras Only"); } @Override public void actionPerformed(ActionEvent e) { ZoneRenderer renderer = TabletopTool.getFrame().getCurrentZoneRenderer(); for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); if (token.hasLightSourceType(LightSource.Type.AURA)) { token.removeLightSourceType(LightSource.Type.AURA); } renderer.flush(token); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); renderer.getZone().putToken(token); } renderer.repaint(); } } public class ClearGMAurasOnlyAction extends AbstractAction { public ClearGMAurasOnlyAction() { super("Clear GM Auras Only"); } @Override public void actionPerformed(ActionEvent e) { ZoneRenderer renderer = TabletopTool.getFrame().getCurrentZoneRenderer(); for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); if (token.hasGMAuras()) { token.removeGMAuras(); } renderer.flush(token); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); renderer.getZone().putToken(token); } renderer.repaint(); } } public class ClearOwnerAurasOnlyAction extends AbstractAction { public ClearOwnerAurasOnlyAction() { super("Clear Owner Auras Only"); } @Override public void actionPerformed(ActionEvent e) { ZoneRenderer renderer = TabletopTool.getFrame().getCurrentZoneRenderer(); for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); if (token.hasOwnerOnlyAuras()) { token.removeOwnerOnlyAuras(); } renderer.flush(token); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); renderer.getZone().putToken(token); } renderer.repaint(); } } public class ClearLightAction extends AbstractAction { public ClearLightAction() { super("Clear All"); } @Override public void actionPerformed(ActionEvent e) { ZoneRenderer renderer = TabletopTool.getFrame().getCurrentZoneRenderer(); for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); token.clearLightSources(); renderer.flush(token); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); renderer.getZone().putToken(token); } renderer.repaint(); } } public class SnapToGridAction extends AbstractAction { private final boolean snapToGrid; private final ZoneRenderer renderer; public SnapToGridAction(boolean snapToGrid, ZoneRenderer renderer) { super("Snap to grid"); this.snapToGrid = snapToGrid; this.renderer = renderer; } @Override public void actionPerformed(ActionEvent e) { for (GUID guid : selectedTokenSet) { Zone zone = renderer.getZone(); Token token = zone.getToken(guid); if (token == null) { continue; } token.setSnapToGrid(!snapToGrid); Grid grid = zone.getGrid(); Dimension offset = grid.getCellOffset(); if (token.isSnapToGrid()) { if (grid.getCapabilities().isSnapToGridSupported()) { ZonePoint zp = new ZonePoint(token.getX() - offset.width, token.getY() - offset.height); zp = grid.convert(grid.convert(zp)); token.setX(zp.x); token.setY(zp.y); } } else { // If SnapToGrid is now off, change the (x,y) coordinates based on the cell offset being used by the grid token.setX(token.getX() + offset.width); token.setY(token.getY() + offset.height); } TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); } } } /** * Internal class used to handle token state changes. */ public class ChangeStateAction extends AbstractAction { /** * Initialize a state action for a given state. * * @param state * The name of the state set when this action is executed */ public ChangeStateAction(String state) { putValue(ACTION_COMMAND_KEY, state); // Set the state command // Load the name, mnemonic, accelerator, and description if // available String key = "defaultTool.stateAction." + state; String name = com.t3.language.I18N.getText(key); if (!name.equals(key)) { putValue(NAME, name); int mnemonic = I18N.getMnemonic(key); if (mnemonic != -1) putValue(MNEMONIC_KEY, mnemonic); String accel = I18N.getAccelerator(key); if (accel != null) putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(accel)); String description = I18N.getDescription(key); if (description != null) putValue(SHORT_DESCRIPTION, description); } else { // Default name if no I18N set putValue(NAME, state); } // endif } /** * Set the state for all of the selected tokens. * * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ @Override public void actionPerformed(ActionEvent aE) { ZoneRenderer renderer = TabletopTool.getFrame().getCurrentZoneRenderer(); for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); if (aE.getActionCommand().equals("clear")) { token.removeAllStates(); token.removeAllBars(); } else { token.setState(aE.getActionCommand(), ((JCheckBoxMenuItem) aE.getSource()).isSelected()); } renderer.flush(token); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); } renderer.repaint(); } } public class ChangeSizeAction extends AbstractAction { private final TokenFootprint footprint; public ChangeSizeAction(TokenFootprint footprint) { super(footprint.getName()); this.footprint = footprint; } /* * (non-Javadoc) * * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ @Override public void actionPerformed(ActionEvent e) { ZoneRenderer renderer = TabletopTool.getFrame().getCurrentZoneRenderer(); for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); token.setFootprint(renderer.getZone().getGrid(), footprint); token.setSnapToScale(true); renderer.flush(token); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); } renderer.repaint(); } } public class VisibilityAction extends AbstractAction { { putValue(Action.NAME, "Visible to players"); } @Override public void actionPerformed(ActionEvent e) { for (GUID guid : selectedTokenSet) { Token token = renderer.getZone().getToken(guid); if (token == null) { continue; } token.setVisible(((JCheckBoxMenuItem) e.getSource()).isSelected()); TabletopTool.getFrame().updateTokenTree(); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); // TODO: Need a better way of indicating local changes renderer.getZone().putToken(token); } renderer.repaint(); } } public class BringToFrontAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { TabletopTool.serverCommand().bringTokensToFront(renderer.getZone().getId(), selectedTokenSet); TabletopTool.getFrame().refresh(); } } public class SendToBackAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { TabletopTool.serverCommand().sendTokensToBack(renderer.getZone().getId(), selectedTokenSet); TabletopTool.getFrame().refresh(); } } public class ImpersonateAction extends AbstractAction { public ImpersonateAction() { putValue(Action.NAME, "Impersonate"); } @Override public void actionPerformed(ActionEvent e) { JTextComponent commandArea = TabletopTool.getFrame().getCommandPanel().getCommandTextArea(); if (!AppUtil.playerOwns(tokenUnderMouse)) { return; } commandArea.setText("/im " + tokenUnderMouse.getId()); TabletopTool.getFrame().getCommandPanel().commitCommand(); commandArea.requestFocusInWindow(); } } public class StartMoveAction extends AbstractAction { public StartMoveAction() { putValue(Action.NAME, "Move"); } @Override public void actionPerformed(ActionEvent e) { Tool tool = TabletopTool.getFrame().getToolbox().getSelectedTool(); if (tool instanceof PointerTool) { ((PointerTool) tool).startTokenDrag(tokenUnderMouse); } else if (tool instanceof StampTool) { ((StampTool) tool).startTokenDrag(tokenUnderMouse); } } } public class ShowPropertiesDialogAction extends AbstractAction { public ShowPropertiesDialogAction() { putValue(Action.NAME, "Edit ..."); } @Override public void actionPerformed(ActionEvent e) { EditTokenDialog dialog = TabletopTool.getFrame().getTokenPropertiesDialog(); dialog.showDialog(tokenUnderMouse); if (dialog.isTokenSaved()) { getRenderer().repaint(); TabletopTool.serverCommand().putToken(getRenderer().getZone().getId(), getTokenUnderMouse()); getRenderer().getZone().putToken(getTokenUnderMouse()); TabletopTool.getFrame().resetTokenPanels(); } } } public class DeleteAction extends AbstractAction { public DeleteAction() { putValue(Action.NAME, "Delete"); } @Override public void actionPerformed(ActionEvent e) { // check to see if this is the required action if (!TabletopTool.confirmTokenDelete()) { return; } for (GUID tokenGUID : selectedTokenSet) { Token token = renderer.getZone().getToken(tokenGUID); if (AppUtil.playerOwns(token)) { renderer.getZone().removeToken(tokenGUID); TabletopTool.serverCommand().removeToken(renderer.getZone().getId(), tokenGUID); } } } } }